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

11456
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", "version": "0.0.2",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
@ -10,43 +10,44 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^14.0.0", "@angular/animations": "^15.2.9",
"@angular/cdk": "^14.2.1", "@angular/cdk": "^15.2.9",
"@angular/common": "^14.0.0", "@angular/common": "^15.2.9",
"@angular/compiler": "^14.0.0", "@angular/compiler": "^15.2.9",
"@angular/core": "^14.0.0", "@angular/core": "^15.2.9",
"@angular/fire": "^7.4.1", "@angular/fire": "^7.5.0",
"@angular/forms": "^14.0.0", "@angular/forms": "^15.2.9",
"@angular/material": "^14.2.1", "@angular/material": "^15.2.9",
"@angular/platform-browser": "^14.0.0", "@angular/platform-browser": "^15.2.9",
"@angular/platform-browser-dynamic": "^14.0.0", "@angular/platform-browser-dynamic": "^15.2.9",
"@angular/router": "^14.0.0", "@angular/router": "^15.2.9",
"@angular/service-worker": "^14.0.0", "@angular/service-worker": "^15.2.9",
"@fortawesome/angular-fontawesome": "^0.11.1", "@fortawesome/angular-fontawesome": "^0.12.1",
"@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^6.2.0", "@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@types/web": "^0.0.99",
"angular-moment-timezone": "^1.7.1", "angular-moment-timezone": "^1.7.1",
"barcode-2-svg": "^0.3.3", "barcode-2-svg": "^0.3.3",
"firebase": "^9.9.3", "firebase": "^9.9.3",
"google-libphonenumber": "^3.2.30", "google-libphonenumber": "^3.2.30",
"jsbarcode": "^3.11.5", "jsbarcode": "^3.11.5",
"libphonenumber-js": "^1.10.28", "libphonenumber-js": "^1.10.28",
"ng-qrcode": "^7.0.0", "ng-qrcode": "^8.0.1",
"ngx-mat-intl-tel-input": "^5.0.0", "ngx-mat-intl-tel-input": "^5.0.0",
"ngx-sharebuttons": "^11.0.0", "ngx-sharebuttons": "^11.0.0",
"primeicons": "^5.0.0", "primeicons": "^5.0.0",
"primeng": "^14.0.1", "primeng": "^15.4.1",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^14.0.6", "@angular-devkit/build-angular": "^15.2.8",
"@angular/cli": "~14.0.6", "@angular/cli": "~15.2.8",
"@angular/compiler-cli": "^14.0.0", "@angular/compiler-cli": "^15.2.9",
"@types/google-libphonenumber": "^7.4.23", "@types/google-libphonenumber": "^7.4.23",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"jasmine-core": "~4.1.0", "jasmine-core": "~4.1.0",
@ -55,6 +56,6 @@
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0", "karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.7.2" "typescript": "4.8"
} }
} }

View File

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

View File

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

View File

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

View File

@ -16,7 +16,6 @@ import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DialogService } from 'primeng/dynamicdialog'; import { DialogService } from 'primeng/dynamicdialog';
import { BonusProgramComponent } from './pages/account/bonus-program/bonus-program.component'; 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 { OrderInfoComponent } from './components/order-info/order-info.component';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
@ -49,7 +48,7 @@ import {
MatBottomSheetModule, MatBottomSheetModule,
MatBottomSheetRef, MatBottomSheetRef,
} from '@angular/material/bottom-sheet'; } 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'; import { DirectivesModule } from './directives/directives.module';
@NgModule({ @NgModule({
@ -62,7 +61,6 @@ import { DirectivesModule } from './directives/directives.module';
AccountComponent, AccountComponent,
ExitComponent, ExitComponent,
BonusProgramComponent, BonusProgramComponent,
OrdersComponent,
OrderInfoComponent, OrderInfoComponent,
FooterButtonsComponent, FooterButtonsComponent,
UserDataComponent, UserDataComponent,
@ -76,7 +74,7 @@ import { DirectivesModule } from './directives/directives.module';
SocialMediaButtonsComponent, SocialMediaButtonsComponent,
LoginComponent, LoginComponent,
// FocusNextInputDirective, // FocusNextInputDirective,
LoyalityProgramComponent, LoyalityProgramComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -120,4 +118,4 @@ import { DirectivesModule } from './directives/directives.module';
], ],
bootstrap: [AppComponent], 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 { CommonModule } from '@angular/common';
import { FocusNextInputDirective } from './focus-next-input.directive'; import { FocusNextInputDirective } from './focus-next-input.directive';
import { DownloadAppDirective } from './download-app.directive'; import { DownloadAppDirective } from './download-app.directive';
import { UpdateOutlineGapDirective } from './update-outline-gap.directive';
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule CommonModule
], ],
declarations: [FocusNextInputDirective, DownloadAppDirective, UpdateOutlineGapDirective], declarations: [FocusNextInputDirective, DownloadAppDirective],
exports: [FocusNextInputDirective, DownloadAppDirective, UpdateOutlineGapDirective] exports: [FocusNextInputDirective, DownloadAppDirective]
}) })
export class DirectivesModule { } export class DirectivesModule { }

View File

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

View File

@ -9,15 +9,21 @@ export class FocusNextInputDirective {
constructor(private renderer: Renderer2) { } constructor(private renderer: Renderer2) { }
ngOnInit() { ngOnInit() {
this.eventEmitter.subscribe(elementId => { // TODO:
try { // 1: don't need to listen all events
this.renderer.selectRootElement(elementId).focus(); // 2: not working in safari
this.renderer.selectRootElement(elementId).click(); this.eventEmitter.subscribe(elementId => {
} catch (ex) { try {
(document.activeElement as HTMLElement).blur(); const element = this.renderer.selectRootElement(elementId)
// If the element doesn't exist or if the element disappears when this called then no need to do anything 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, UserData,
} }
export interface Moment extends moment.Moment {} export interface Moment extends moment.Moment { }
export interface Page { export interface Page {
code: PageCode; code: PageCode;
@ -74,10 +74,10 @@ export interface OrderStatus {
} }
export interface lvlPeriod { export interface lvlPeriod {
percent: number; percent: number;
start: number; start: number;
end?: number; end?: number;
color: string; color: string;
} }
export interface DeliveryType { export interface DeliveryType {
@ -179,3 +179,52 @@ export interface UserData {
city: string; city: string;
phone: string | null; 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

@ -31,7 +31,7 @@ export class RefSystemComponent implements OnInit {
true true
) )
)).data )).data
this.refUrl += accountData.user_id this.refUrl += accountData.user_id
this.loading = false this.loading = false
} }
@ -53,12 +53,14 @@ export class RefSystemComponent implements OnInit {
copyUrl() { copyUrl() {
navigator.clipboard.writeText(this.refUrl) navigator.clipboard.writeText(this.refUrl)
.then(() => { .then(() => {
this.messageService.clear();
this.messageService.add({ this.messageService.add({
severity: 'custom', severity: 'custom',
summary: 'Ссылка скопирована!', summary: 'Ссылка скопирована!',
}); });
}) })
.catch(err => { .catch(err => {
this.messageService.clear();
this.messageService.add({ this.messageService.add({
severity: 'error', severity: 'error',
summary: 'Произошла ошибка!', summary: 'Произошла ошибка!',

View File

@ -38,9 +38,9 @@ export class LoginComponent implements OnInit, AfterViewInit {
private jsonrpc: JsonrpcService, private jsonrpc: JsonrpcService,
private messageService: MessageService, private messageService: MessageService,
private _snackBar: MatSnackBar private _snackBar: MatSnackBar
) {} ) { }
ngOnInit(): void {} ngOnInit(): void { }
ngAfterViewInit() { ngAfterViewInit() {
setTimeout(() => { setTimeout(() => {
@ -72,7 +72,7 @@ export class LoginComponent implements OnInit, AfterViewInit {
const nextInputIndex = index + 1; const nextInputIndex = index + 1;
if (event.target.value.length > 1) { if (event.target.value.length > 1) {
event.target.value = event.target.value.slice(-1); event.target.value = event.target.value.slice(-1);
} }
if (nextInputIndex > 0 && nextInputIndex <= this.inputIds.length) { if (nextInputIndex > 0 && nextInputIndex <= this.inputIds.length) {
this.inputFocusEmitter.emit(`#${this.inputIds[nextInputIndex]}`); this.inputFocusEmitter.emit(`#${this.inputIds[nextInputIndex]}`);
@ -85,6 +85,7 @@ export class LoginComponent implements OnInit, AfterViewInit {
const data = this.phoneForm.value; const data = this.phoneForm.value;
this.isShowNumber = false; this.isShowNumber = false;
if (this.timeLeft) { if (this.timeLeft) {
this.messageService.clear();
this.messageService.add({ this.messageService.add({
severity: 'custom', severity: 'custom',
summary: `Отправить повторно можно через ${this.timeLeft}с`, summary: `Отправить повторно можно через ${this.timeLeft}с`,
@ -95,29 +96,29 @@ export class LoginComponent implements OnInit, AfterViewInit {
method: 'sendVerifyByPhone', method: 'sendVerifyByPhone',
params: [data.phone] params: [data.phone]
}, RpcService.authService, false).subscribe({ }, RpcService.authService, false).subscribe({
next: (result) => { next: (result) => {
if (result.code !== 0) { if (result.code !== 0) {
this._snackBar.open('Произошла ошибка! Попробуйте позже', '', { this._snackBar.open('Произошла ошибка! Попробуйте позже', '', {
duration: 3000 duration: 3000
}) })
}
if (result.code === 0) {
this.timeLeft = 60;
const interval = setInterval(() => {
if(this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
},1000)
}
this.isShowNumber = false;
},
error: (error) => {
console.error('Error: ', error);
} }
if (result.code === 0) {
this.timeLeft = 60;
const interval = setInterval(() => {
if (this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
}, 1000)
}
this.isShowNumber = false;
},
error: (error) => {
console.error('Error: ', error);
} }
}
); );
setTimeout(() => { setTimeout(() => {
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`); this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
@ -130,28 +131,28 @@ export class LoginComponent implements OnInit, AfterViewInit {
method: 'getTokenByPhone', method: 'getTokenByPhone',
params: [this.phoneForm.value.phone, Object.values(data).join('')] params: [this.phoneForm.value.phone, Object.values(data).join('')]
}, RpcService.authService, false).subscribe({ }, RpcService.authService, false).subscribe({
next: (result) => { next: (result) => {
if (result.code === 0) { if (result.code === 0) {
this.cookiesService.setCookie('token', result?.data?.token); this.cookiesService.setCookie('token', result?.data?.token);
this.router.navigate(['/'], { this.router.navigate(['/'], {
queryParams: { queryParams: {
token: result?.data?.token token: result?.data?.token
}, },
}); });
// this.phoneConfirmed.emit(null); // this.phoneConfirmed.emit(null);
} else if (result.code === 230) { } else if (result.code === 230) {
this._snackBar.open('Неверный код!', '', { this._snackBar.open('Неверный код!', '', {
duration: 3000 duration: 3000
}) })
// this.errorConfirmCode = true; // this.errorConfirmCode = true;
}
},
error: (error) => {
console.error(error);
} }
},
error: (error) => {
console.error(error);
} }
}
); );
} }

View File

@ -61,12 +61,13 @@ export class MainComponent implements OnInit {
const userAgent = window.navigator.userAgent.toLowerCase(); const userAgent = window.navigator.userAgent.toLowerCase();
const ios = /iphone|ipod|ipad/.test(userAgent); const ios = /iphone|ipod|ipad/.test(userAgent);
if (ios) { if (ios) {
this.messageService.clear();
this.messageService.add({ this.messageService.add({
severity: 'custom', severity: 'custom',
summary: `Чтобы получать уведомления, добавьте карту в Apple Wallet`, summary: `Чтобы получать уведомления, добавьте карту в Apple Wallet`,
life: 5000, life: 5000,
}); });
// var permissionData = window.safari.pushNotification.permission('web.com.example.domain'); // var permissionData = window.safari.pushNotification.permission('web.com.example.domain');
// $scope.checkRemotePermission(permissionData); // $scope.checkRemotePermission(permissionData);
} else { } else {

View File

@ -1,22 +1,22 @@
<h2>Ваш предыдущий заказ</h2> <h2>Ваш предыдущий заказ</h2>
<div class="info-order"> <div class="info-order">
<p class="flex"><span>Дата: </span> <ng-container *ngIf="lastOrder">
<span *ngIf="!loading">{{(lastOrder?.transactionCreateDate | date:'dd.MM.yyyyг.') || 'Данные не найдены'}}</span> <p class="flex"><span>Дата: </span>
<ng-container *ngIf="loading"> <span *ngIf="!loading">{{lastOrder!.last_purchase_date}}</span>
<ng-container </p>
*ngTemplateOutlet="spinner; context: { $implicit: 24 }" <p class="flex"><span>На сумму: </span>
></ng-container> <span *ngIf="!loading">{{lastOrder?.last_purchase_sum}}</span>
</ng-container> </p>
</p> </ng-container>
<p class="flex"><span>На сумму: </span> <ng-container *ngIf="!lastOrder">
<span *ngIf="!loading">{{lastOrder?.orderSum ? lastOrder?.orderSum + ' ₽' : 'Данные не найдены'}}</span> <p class="flex">
<ng-container *ngIf="loading"> <span>Данные не найдены</span>
<ng-container </p>
*ngTemplateOutlet="spinner; context: { $implicit: 24 }" </ng-container>
></ng-container>
</ng-container>
</p>
</div> </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"> <a href="https://yandex.ru/profile/151770398186" target="_blank">
<button class="evaluate-order">Оценить заказ</button> <button class="evaluate-order">Оценить заказ</button>
</a> </a>
@ -28,4 +28,4 @@
<ng-template #spinner let-diameter> <ng-template #spinner let-diameter>
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner> <mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
</ng-template> </ng-template>

View File

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

View File

@ -1,5 +1,5 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { Purchase } from 'src/app/interface/data'; import { LastPurchase } from 'src/app/interface/data';
@Component({ @Component({
selector: 'app-last-order[lastOrder]', selector: 'app-last-order[lastOrder]',
@ -7,12 +7,11 @@ import { Purchase } from 'src/app/interface/data';
styleUrls: ['./last-order.component.scss'] styleUrls: ['./last-order.component.scss']
}) })
export class LastOrderComponent implements OnInit { export class LastOrderComponent implements OnInit {
@Input() lastOrder!: Purchase; @Input() lastOrder?: LastPurchase;
@Input() loading!: boolean; @Input() loading!: boolean;
constructor() { } constructor() { }
ngOnInit(): void { 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"> <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> <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 class="plug"></div> -->
<button mat-stroked-button appDownloadApp>Установить</button> <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> </div>

View File

@ -1,34 +1,83 @@
:host { :host {
width: 100%; width: 100%;
.notification {
img {
width: 28px;
height: 28px;
}
}
} }
.container { .container {
box-sizing: border-box; max-width: 600px;
padding: 12px 16px; margin: 0 auto;
width: 100%; box-sizing: border-box;
background: var(--button-color); padding: 12px 16px;
color: var(--button-text-color); width: 100%;
display: grid; background: var(--button-color);
justify-content: space-between; color: var(--button-text-color);
grid-template-columns: auto auto auto;
justify-items: center;
align-items: center;
.back-arrow {
font-size: 16px;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; gap: 10px;
} .block {
.plug { display: flex;
height: 24px; justify-content: space-between;
width: 24px; align-items: center;
visibility: hidden; }
} .back {
.title { width: 40px;
font-family: "Montserrat", sans-serif; }
font-weight: 700; .back-arrow {
font-size: 17px; font-size: 16px;
line-height: 22px; display: flex;
margin: 0; align-items: center;
} justify-content: center;
}
.plug {
height: 24px;
width: 24px;
visibility: hidden;
}
.title {
font-weight: 700;
font-size: 17px;
line-height: 22px;
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({ @Component({
selector: 'app-navbar[title]', selector: 'app-navbar[title]',
@ -7,15 +14,69 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
}) })
export class NavbarComponent implements OnInit { export class NavbarComponent implements OnInit {
@Input() title: string = 'Название не задано' @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 { ngOnInit(): void {
const deviceType = getTypeDevice();
this.showMenu = deviceType === DeviceType.ios;
}
toggleMenu() {
this.showDropdown = !this.showDropdown;
}
openMenu() {
this.showDropdown = true;
}
closeMenu = () => {
this.showDropdown = false;
} }
back() { 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[] = [ public links: ISocialMediaLink[] = [
{ {
label: 'Инстаграм', label: 'Инстаграм',
url: 'https://www.instagram.com/', url: 'https://instagram.com/coffeelike_com?igshid=MzRlODBiNWFlZA==',
imgUrl: '/assets/social-media-icons/instagram.svg' imgUrl: '/assets/social-media-icons/instagram.svg'
}, },
{ {

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { IndexComponent } from './components/index/index.component';
import { GuestCardComponent } from './pages/guest-card/guest-card.component'; import { GuestCardComponent } from './pages/guest-card/guest-card.component';
import { AuthGuard } from 'src/app/guards/auth-guard.guard'; import { AuthGuard } from 'src/app/guards/auth-guard.guard';
import { LoginComponent } from './pages/login/login.component'; import { LoginComponent } from './pages/login/login.component';
@ -16,14 +15,14 @@ const routes: Routes = [
path: 'login', path: 'login',
component: LoginComponent component: LoginComponent
}, },
{ {
path: 'loyality-program', path: 'loyality-program',
component: LoyalityProgramComponent, component: LoyalityProgramComponent,
} }
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], 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 { FooterComponent } from './components/footer/footer.component';
import { SocialMediaButtonsComponent } from './components/social-media-buttons/social-media-buttons.component'; import { SocialMediaButtonsComponent } from './components/social-media-buttons/social-media-buttons.component';
import { NavbarComponent } from './components/navbar/navbar.component'; import { NavbarComponent } from './components/navbar/navbar.component';
import { MenuItemComponent } from './components/navbar/menu_item.component';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { GuestCardComponent } from './pages/guest-card/guest-card.component'; import { GuestCardComponent } from './pages/guest-card/guest-card.component';
import { QrCodeModule } from 'ng-qrcode'; import { QrCodeModule } from 'ng-qrcode';
@ -19,7 +20,9 @@ import { NgxMatIntlTelInputComponent } from 'ngx-mat-intl-tel-input';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { DirectivesModule } from 'src/app/directives/directives.module'; import { DirectivesModule } from 'src/app/directives/directives.module';
import { LoyalityProgramComponent } from './pages/loyality-program/loyality-program.component'; 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({ @NgModule({
declarations: [ declarations: [
@ -32,9 +35,11 @@ import {MatButtonModule} from '@angular/material/button';
LastOrderComponent, LastOrderComponent,
InviteFriendsComponent, InviteFriendsComponent,
LoginComponent, LoginComponent,
LoyalityProgramComponent LoyalityProgramComponent,
MenuItemComponent,
], ],
imports: [ imports: [
ToastModule,
CommonModule, CommonModule,
DefaultOptionRoutingModule, DefaultOptionRoutingModule,
MatIconModule, MatIconModule,
@ -50,4 +55,4 @@ import {MatButtonModule} from '@angular/material/button';
], ],
bootstrap: [IndexComponent], bootstrap: [IndexComponent],
}) })
export class DefaultOptionModule {} export class DefaultOptionModule { }

View File

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

View File

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

View File

@ -1,16 +1,11 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet'; 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 { ExitComponent } from 'src/app/components/exit/exit.component';
import { CookiesService } from 'src/app/services/cookies.service'; import { CookiesService } from 'src/app/services/cookies.service';
import { WpJsonService } from 'src/app/services/wp-json.service'; import { MessageService } from 'primeng/api';
import { environment } from 'src/environments/environment'; import { MessagingService } from 'src/app/services/messaging.service';
import moment from 'moment'; import { DeviceType, getTypeDevice } from 'src/app/utils';
import { Moment, Purchase, lvlPeriod } from 'src/app/interface/data'; import { AuthService } from 'src/app/services/auth.service';
import { lvlPeriods } from 'src/app/app.constants';
import { LoyaltyProgramService } from 'src/app/services/loyalty-program.service';
@Component({ @Component({
selector: 'app-guest-card', selector: 'app-guest-card',
@ -20,46 +15,41 @@ import { LoyaltyProgramService } from 'src/app/services/loyalty-program.service'
export class GuestCardComponent implements OnInit { export class GuestCardComponent implements OnInit {
public qrCodeSize: number = 85; public qrCodeSize: number = 85;
private isQrCodeClicked: boolean = false; private isQrCodeClicked: boolean = false;
public customerInfo!: any; public Math: Math = Math;
public purchases!: Purchase[]; public showBack: boolean = false;
public lastPurchase!: Purchase;
public Math: Math = Math;
constructor( constructor(
private _bottomSheet: MatBottomSheet, private _bottomSheet: MatBottomSheet,
public cookiesService: CookiesService, public cookiesService: CookiesService,
private router: Router, private messagingService: MessagingService,
private wpJsonService: WpJsonService, private messageService: MessageService,
public loyaltyProgram: LoyaltyProgramService public authService: AuthService,
) {} ) { }
ngOnInit(): void { ngOnInit(): void {
const token = this.cookiesService.getItem('token'); this.showBack = getTypeDevice() === DeviceType.android;
this.loyaltyProgram.purchaseData.$loading = true;
this.wpJsonService this.requestPermission();
.getCustomerInfo( }
environment.systemId,
token || '', requestPermission() {
environment.icardProxy const userAgent = window.navigator.userAgent.toLowerCase();
) const ios = /iphone|ipod|ipad/.test(userAgent);
.subscribe({ if (ios) {
next: (value) => { this.messageService.clear();
this.customerInfo = value.customer_info; this.messageService.add({
this.cookiesService.setCookie('phone-number', this.customerInfo?.phone?.substr(2)) severity: 'custom',
this.getPurchases().subscribe((value) => { summary: `Чтобы получать уведомления, добавьте карту в Apple Wallet`,
this.purchases = this.loyaltyProgram.filterPurchases(value[this.customerInfo?.id]) life: 5000,
this.lastPurchase = this.loyaltyProgram.getLastPurchase(this.purchases)
this.loyaltyProgram.setLastPurchases(this.purchases)
this.loyaltyProgram.setCurrentPurchases(this.purchases)
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;
});
},
}); });
// 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() { qrCodeClick() {
@ -69,31 +59,16 @@ export class GuestCardComponent implements OnInit {
deleteToken(): void { deleteToken(): void {
this.cookiesService.logout(); this.cookiesService.logout();
} }
logout() { logout = () => {
const bottomSheet = this._bottomSheet.open(ExitComponent); const bottomSheet = this._bottomSheet.open(ExitComponent);
bottomSheet.afterDismissed().subscribe({ bottomSheet.afterDismissed().subscribe({
next: (val) => { next: (val) => {
if (val) { if (val) {
this.deleteToken(); this.authService.logout();
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
);
}
} }

View File

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

View File

@ -1,10 +1,11 @@
:host { :host {
padding-top: 48px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
max-width: 600px; max-width: 600px;
margin: 0 auto 52px; margin: 0 auto 52px;
padding-left: 20px;
padding-right: 20px;
h1 { h1 {
margin-top: 20px; margin-top: 20px;
@ -46,15 +47,16 @@
.offer { .offer {
margin-top: 10px; margin-top: 10px;
padding: 0 16px; padding: 0 16px;
font-family: "Gowun Dodum";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-size: 12px; font-size: 12px;
line-height: 17px; line-height: 17px;
text-align: center; text-align: center;
span {
color: var(--button-color); a {
text-decoration: none;
color: var(--button-color);
} }
} }

View File

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

View File

@ -1,105 +1,105 @@
<app-navbar title="Программа лояльности" (backEvent)="goBack()"></app-navbar> <app-navbar title="Программа лояльности" [backEvent]="goBack"></app-navbar>
<ng-container *ngIf="loyaltyProgram.currentLvlPeriod"> <ng-container *ngIf="authService.currentLvlPeriod">
<div class="loyality-program"> <div class="loyality-program">
<app-accordion header="Условия начисления бонусов"> <app-accordion header="Условия начисления бонусов">
<p> <p>
Ваш текущий уровень {{ loyaltyProgram.currentLvl }}, Ваш текущий уровень {{ authService.userInfo?.current_level_and_cashback?.current_level ?? '--' }},
поэтому вам начисляется {{ loyaltyProgram.currentLvlPeriod.percent }}% от суммы покупки. поэтому вам начисляется {{ authService.currentLvlPeriod.percent }}% от суммы покупки.
</p> </p>
<p> <p>
Смена уровня произойдет в начале следующего квартала, Смена уровня произойдет в начале следующего квартала,
{{ loyaltyProgram.purchaseData.currentPeriod[1] {{ authService.currentPeriod[1]
.locale("ru") .locale("ru")
.format("DD.MM.YY") }}. .format("DD.MM.YY") }}.
</p> </p>
</app-accordion> </app-accordion>
<app-accordion header="Уровни бонусной программы"> <app-accordion header="Уровни бонусной программы">
<p> <p>
Начисление Бонусных баллов происходит по дифференцированной шкале в Начисление Бонусных баллов происходит по дифференцированной шкале в
зависимости от уровня: зависимости от уровня:
</p> </p>
<ng-container
*ngFor="let item of lvlPeriods; let index = index; let last = last"
>
<ng-container *ngIf="!last">
<ul>
<span [style]="{ color: item.color }">Уровень {{ index + 1 }}</span>
<li>
Сумма покупок за прошлый квартал {{ item.start }}-{{ item.end }}
руб.
</li>
<li>Начисляемый бонус {{ item.percent }}% от суммы покупки</li>
</ul>
<br />
</ng-container>
<ng-container *ngIf="last">
<ul>
<span [style]="{ color: item.color }">Уровень {{ index + 1 }}</span>
<li>Сумма покупок за прошлый квартал — от {{ item.start }} руб.</li>
<li>Начисляемый бонус, в % от суммы покупки - {{ item.percent }}%</li>
</ul>
</ng-container>
</ng-container>
</app-accordion>
<app-accordion header="Условия «оплаты» покупки бонусами">
<p>
Участник может использовать Бонусы для «оплаты» до 100% стоимости любой
покупки.
</p>
<p>
Списание Бонусов происходит из расчета 1:1 (один Бонус дает скидку 1
российский рубль / 1 тенге / 1 белорусский рубль. Скидка, предоставляемая
Участнику при списании Бонусов, уменьшает цену товаров в заказе в
соответствии с условиями ПЛ.
</p>
<p>
Для списания Бонусов Участник должен попросить об этом в кофе-баре сети
«COFFEE LIKE» кассира до момента пробития фискального чека, после чего им
будет проверена возможность списания Бонусов.
</p>
<p>
Для всех Участников возможно списание без использования мобильного
приложения.
</p>
<p>Полученные Бонусы не подлежат обмену на денежные средства.</p>
</app-accordion>
<app-accordion header="Особые условия">
<p>
Начисленные на счет бонусы сгорают по прошествии 90 дней с момента
совершения последней покупки с начислением или списанием бонусов.
</p>
<ng-container
*ngFor="let item of lvlPeriods; let index = index; let last = last"
>
<ng-container *ngIf="!last">
<ul> <ul>
<span [style]="{ color: item.color }">Уровень {{ index + 1 }}</span> Возврат покупки, за которую бонусы были начислены:
<li> <li>
Сумма покупок за прошлый квартал {{ item.start }}-{{ item.end }} В случае, если бонусов на счету достаточно для списания, бонусы
руб. списываются в полном ранее начисленном за возвращаемый товар объеме.
</li> </li>
<li>Начисляемый бонус {{ item.percent }}% от суммы покупки</li> <li>
В случае, если бонусов на счету недостаточно, формируется минусовой
баланс.
</li>
</ul> </ul>
<br />
</ng-container>
<ng-container *ngIf="last">
<ul> <ul>
<span [style]="{ color: item.color }">Уровень {{ index + 1 }}</span> Возврат покупки, которая была оплачена бонусами:
<li>Сумма покупок за прошлый квартал — от {{ item.start }} руб.</li> <li>
<li>Начисляемый бонус, в % от суммы покупки - {{ item.percent }}%</li> В случае предъявления Участником кассового или товарного чека, сумма
бонусов, списанная для оплаты возвращаемого товара, зачисляется на счет
участника.
</li>
<li>
В случае возврата товара с применением оплаты бонусами, клиенту
возвращается денежная сумма в размере, внесенном Участником в оплату
товара при покупке, за вычетом суммы, оплаченной бонусами.
</li>
</ul> </ul>
</ng-container> </app-accordion>
</ng-container>
</app-accordion>
<app-accordion header="Условия «оплаты» покупки бонусами"> </div>
<p>
Участник может использовать Бонусы для «оплаты» до 100% стоимости любой
покупки.
</p>
<p>
Списание Бонусов происходит из расчета 1:1 (один Бонус дает скидку 1
российский рубль / 1 тенге / 1 белорусский рубль. Скидка, предоставляемая
Участнику при списании Бонусов, уменьшает цену товаров в заказе в
соответствии с условиями ПЛ.
</p>
<p>
Для списания Бонусов Участник должен попросить об этом в кофе-баре сети
«COFFEE LIKE» кассира до момента пробития фискального чека, после чего им
будет проверена возможность списания Бонусов.
</p>
<p>
Для всех Участников возможно списание без использования мобильного
приложения.
</p>
<p>Полученные Бонусы не подлежат обмену на денежные средства.</p>
</app-accordion>
<app-accordion header="Особые условия">
<p>
Начисленные на счет бонусы сгорают по прошествии 90 дней с момента
совершения последней покупки с начислением или списанием бонусов.
</p>
<ul> <app-footer></app-footer>
Возврат покупки, за которую бонусы были начислены:
<li>
В случае, если бонусов на счету достаточно для списания, бонусы
списываются в полном ранее начисленном за возвращаемый товар объеме.
</li>
<li>
В случае, если бонусов на счету недостаточно, формируется минусовой
баланс.
</li>
</ul>
<ul>
Возврат покупки, которая была оплачена бонусами:
<li>
В случае предъявления Участником кассового или товарного чека, сумма
бонусов, списанная для оплаты возвращаемого товара, зачисляется на счет
участника.
</li>
<li>
В случае возврата товара с применением оплаты бонусами, клиенту
возвращается денежная сумма в размере, внесенном Участником в оплату
товара при покупке, за вычетом суммы, оплаченной бонусами.
</li>
</ul>
</app-accordion>
</div>
<app-footer></app-footer>
</ng-container> </ng-container>

View File

@ -1,3 +1,4 @@
.loyality-program { .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 { Location } from '@angular/common';
import { lvlPeriod } from 'src/app/interface/data'; import { lvlPeriod } from 'src/app/interface/data';
import { lvlPeriods } from 'src/app/app.constants'; 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({ @Component({
selector: 'loyality-program', selector: 'loyality-program',
templateUrl: './loyality-program.component.html', templateUrl: './loyality-program.component.html',
styleUrls: ['./loyality-program.component.scss'], styleUrls: ['./loyality-program.component.scss'],
}) })
export class LoyalityProgramComponent implements OnInit { export class LoyalityProgramComponent {
constructor( constructor(
private _location: Location, private _location: Location,
public loyaltyProgram: LoyaltyProgramService, public authService: AuthService,
) {} ) { }
public lvlPeriods: lvlPeriod[] = lvlPeriods;
goBack() {
this._location.back();
}
ngOnInit(): void { public lvlPeriods: lvlPeriod[] = lvlPeriods;
if(this.loyaltyProgram.currentLvlPeriod == null) {
this._location.back(); goBack = () => {
} 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 { GuestCardComponent } from './pages/guest-card/guest-card.component';
import { QrCodeModule } from 'ng-qrcode'; import { QrCodeModule } from 'ng-qrcode';
import { AccordionComponent } from './components/accordion/accordion.component'; 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 { LastOrderComponent } from './components/last-order/last-order.component';
import { InviteFriendsComponent } from './components/invite-friends/invite-friends.component'; import { InviteFriendsComponent } from './components/invite-friends/invite-friends.component';
import { LoginComponent } from './pages/login/login.component'; import { LoginComponent } from './pages/login/login.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 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 { 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'; import { DirectivesModule } from 'src/app/directives/directives.module';

View File

@ -6,7 +6,7 @@ import {
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; 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 { Router } from '@angular/router';
import { MessageService } from 'primeng/api'; import { MessageService } from 'primeng/api';
import { CookiesService } from 'src/app/services/cookies.service'; import { CookiesService } from 'src/app/services/cookies.service';

View File

@ -1,15 +1,22 @@
import { lastValueFrom } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http'; 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 { 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({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AppleWalletService { export class AppleWalletService {
private url: string = environment.appleWalletEndpoint; private url: string = environment.appleWalletEndpoint;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
) {} private cookiesService: CookiesService,
private jsonrpc: JsonrpcService,
@Inject(DOCUMENT) private document: Document,
) { }
generateCard(token: string, user_id: string) { generateCard(token: string, user_id: string) {
let headers = new HttpHeaders(); 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) 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(); let headers = new HttpHeaders();
headers = headers.set('Authorization', environment.appleWalletSecret); headers = headers.set('Authorization', environment.appleWalletSecret);
const options = { const options = {
@ -32,4 +39,37 @@ export class AppleWalletService {
} }
return this.http.post(`${this.url}/sendNotification/${user_id}`, body, options) 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

@ -26,15 +26,15 @@ export class MessagingService {
method: 'getAdditionalInfo', method: 'getAdditionalInfo',
params: [] params: []
}, RpcService.authService, true) }, RpcService.authService, true)
)).data )).data
let tokens: string[] = [] let tokens: string[] = []
if (typeof additionalInfo['fmc-token'] === 'string') { if (typeof additionalInfo['fmc-token'] === 'string') {
tokens.push(additionalInfo['fmc-token'], token) tokens.push(additionalInfo['fmc-token'], token)
} else if (typeof additionalInfo['fmc-token'] === 'object') { } else if (typeof additionalInfo['fmc-token'] === 'object') {
tokens = [...additionalInfo['fmc-token'], token] tokens = [...additionalInfo['fmc-token'], token]
} else { } else {
tokens = [token] tokens = [token]
} }
this.jsonRpcService this.jsonRpcService
.rpc( .rpc(
{ {
@ -50,6 +50,7 @@ export class MessagingService {
) )
.subscribe({ .subscribe({
next: () => { next: () => {
this.messageService.clear();
this.messageService.add({ this.messageService.add({
severity: 'custom', severity: 'custom',
summary: 'Спасибо за подписку!', summary: 'Спасибо за подписку!',
@ -57,6 +58,7 @@ export class MessagingService {
}, },
error: (err) => { error: (err) => {
console.error('Error: ', err); console.error('Error: ', err);
this.messageService.clear();
this.messageService.add({ this.messageService.add({
severity: 'error', severity: 'error',
summary: 'Произошла ошибка, попробуйте позже', summary: 'Произошла ошибка, попробуйте позже',
@ -65,11 +67,20 @@ export class MessagingService {
}); });
} }
checkRequestPermission() {
return Notification.permission !== 'granted' ? false : true;
}
requestPermission() { requestPermission() {
if (this.checkRequestPermission()) {
return;
}
try { try {
this.angularFireMessaging.requestToken.subscribe({ this.angularFireMessaging.requestToken.subscribe({
next: (token) => { next: (token) => {
this.updateToken(token); this.updateToken(token);
this.receiveMessage();
}, },
error: (e) => console.error(e), error: (e) => console.error(e),
}); });

View File

@ -1,12 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {environment} from "../../environments/environment"; import { environment } from "../../environments/environment";
import {HttpClient, HttpHeaders} from "@angular/common/http"; import { HttpClient, HttpHeaders } from "@angular/common/http";
import {CookiesService} from "./cookies.service"; import { CookiesService } from "./cookies.service";
import {Observable, of, switchMap} from "rxjs"; import { Observable, of, switchMap } from "rxjs";
import {JsonRpcBody} from "./jsonrpc.service"; import { JsonRpcBody } from "./jsonrpc.service";
import {DeliveryType, AcceptedOrder, Product} from "../interface/data"; import { DeliveryType, AcceptedOrder, Product } from "../interface/data";
import {ActivatedRoute} from "@angular/router";
import {Order} from "../models/order";
export enum Method { export enum Method {
@ -23,38 +21,30 @@ export class WpJsonService {
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private cookiesService: CookiesService, private cookiesService: CookiesService,
private route: ActivatedRoute,
) { } ) { }
getDeliveryTypes(): Observable<DeliveryType[]>{ getDeliveryTypes(): Observable<DeliveryType[]> {
return this._request('orders/delivery-types', 'GET'); return this._request('orders/delivery-types', 'GET');
} }
createOrder(order: any){ createOrder(order: any) {
return this._request('orders', 'POST', order); return this._request('orders', 'POST', order);
} }
getOrders(): Observable<AcceptedOrder[]>{ getOrders(): Observable<AcceptedOrder[]> {
return this._request('orders', 'GET', null, true); return this._request('orders', 'GET', null, true);
} }
getProductById(id: number): Observable<Product>{ getProductById(id: number): Observable<Product> {
return this._request(`products/${id}`, 'GET'); return this._request(`products/${id}`, 'GET');
} }
getCustomerInfo(systemId: string, token: string, url: string): Observable<any> { getCustomerInfo(systemId: string, token: string, url: string): Observable<any> {
return this._request(`customer_info/${systemId}/${token}/`, 'GET', null, false, url).pipe( return this._request(`customer_info/${systemId}/${token}/`, 'GET', null, false, url);
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') { getLastPurchase(systemId: string, token: string): Observable<any> {
return of(response) return this._request(`last_trans/${systemId}/${token}/`, 'GET', null, false, environment.icardProxy);
} else {
return this.newCustomer(systemId, token, url).pipe(
switchMap(() => this.getCustomerInfo(systemId, token, url))
)
}
})
)
} }
newCustomer(systemId: string, token: string, url: string): Observable<any> { newCustomer(systemId: string, token: string, url: string): Observable<any> {
@ -83,7 +73,7 @@ export class WpJsonService {
headers = headers.set('Content-Type', 'application/json'); headers = headers.set('Content-Type', 'application/json');
let urlToken = ''; let urlToken = '';
if (token && token !== 'undefined' && auth) { if (token && token !== 'undefined' && auth) {
urlToken = '?token=' + token; urlToken = '?token=' + token;
} }
this.body = body; this.body = body;
const options = { const options = {
@ -96,6 +86,6 @@ export class WpJsonService {
url = baseUrl url = baseUrl
} }
return this.http 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, hasBonusProgram: true,
systemId: 'tsQ2cu59Xz9qgGTm3z', systemId: 'tsQ2cu59Xz9qgGTm3z',
defaultUrl: 'https://club.coffee-like.com', defaultUrl: 'https://club.coffee-like.com',
manifestUrl: 'https://club.coffee-like.com/manifest.webmanifest',
firebase: { firebase: {
apiKey: "AIzaSyCnKvln5itnrBj62POCPHxshAN_Vmd0zds", apiKey: "AIzaSyDTb_xuMz2vDx8xGs34AJiltraKVlwmrtY",
authDomain: "fashionlogicanotification.firebaseapp.com", authDomain: "coffee-like-77bfe.firebaseapp.com",
projectId: "fashionlogicanotification", projectId: "coffee-like-77bfe",
storageBucket: "fashionlogicanotification.appspot.com", storageBucket: "coffee-like-77bfe.appspot.com",
messagingSenderId: "99855572145", messagingSenderId: "1094726277369",
appId: "1:1094726277369:web:8af560662da7700e7a2a28"
appId: "1:99855572145:web:7548c189d61b3bcc92d690",
measurementId: "G-RQF97ZK7R1"
}, },
version: packageJson.version, 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/', icardProxy: 'https://club.coffee-like.com/api/icard-proxy/',
appleWalletSecret: 'Token F5mbzEERAznGKVbB6l', appleWalletSecret: 'Token F5mbzEERAznGKVbB6l',
clientName: 'coffeeLike' clientName: 'coffeelike'
} }

View File

@ -8,18 +8,18 @@ export const environment = {
hasBonusProgram: true, hasBonusProgram: true,
systemId: 'tsQ2cu59Xz9qgGTm3z', systemId: 'tsQ2cu59Xz9qgGTm3z',
defaultUrl: 'http://192.168.0.179:4200', defaultUrl: 'http://192.168.0.179:4200',
manifestUrl: 'https://club.coffee-like.com/manifest.webmanifest',
firebase: { firebase: {
apiKey: 'AIzaSyCnKvln5itnrBj62POCPHxshAN_Vmd0zds', apiKey: "AIzaSyDTb_xuMz2vDx8xGs34AJiltraKVlwmrtY",
authDomain: 'fashionlogicanotification.firebaseapp.com', authDomain: "coffee-like-77bfe.firebaseapp.com",
projectId: 'fashionlogicanotification', projectId: "coffee-like-77bfe",
storageBucket: 'fashionlogicanotification.appspot.com', storageBucket: "coffee-like-77bfe.appspot.com",
messagingSenderId: '99855572145', messagingSenderId: "1094726277369",
appId: '1:99855572145:web:7548c189d61b3bcc92d690', appId: "1:1094726277369:web:8af560662da7700e7a2a28"
measurementId: 'G-RQF97ZK7R1',
}, },
version: packageJson.version, version: packageJson.version,
appleWalletEndpoint: 'http://192.168.0.179:4200/apns/api', appleWalletEndpoint: 'https://apple-wallet-iiko.it-retail.tech/apns/api',
icardProxy: 'http://192.168.0.14:4200/icard-proxy/', icardProxy: 'https://club.coffee-like.com/api/icard-proxy/',
appleWalletSecret: 'Token F5mbzEERAznGKVbB6l', 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'); importScripts('https://www.gstatic.com/firebasejs/3.6.9/firebase-messaging.js');
firebase.initializeApp({ firebase.initializeApp({
apiKey: "AIzaSyCnKvln5itnrBj62POCPHxshAN_Vmd0zds", apiKey: "AIzaSyDTb_xuMz2vDx8xGs34AJiltraKVlwmrtY",
authDomain: "fashionlogicanotification.firebaseapp.com", authDomain: "coffee-like-77bfe.firebaseapp.com",
projectId: "fashionlogicanotification", projectId: "coffee-like-77bfe",
storageBucket: "fashionlogicanotification.appspot.com", storageBucket: "coffee-like-77bfe.appspot.com",
messagingSenderId: "99855572145", messagingSenderId: "1094726277369",
appId: "1:99855572145:web:7548c189d61b3bcc92d690", appId: "1:1094726277369:web:8af560662da7700e7a2a28"
measurementId: "G-RQF97ZK7R1"
}); });
const messaging = firebase.messaging(); const messaging = firebase.messaging();

View File

@ -50,5 +50,9 @@
"purpose": "maskable any" "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} article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}
html{height:100%;} html{height:100%;}
body{line-height:1} body{line-height:1}
@ -27,7 +111,7 @@ body {
} }
:root { :root {
--main-color: #00b26b; --main-color: #7F2061;
--main-border-radius: 35px; --main-border-radius: 35px;
--background-color: #ffffff; --background-color: #ffffff;
@ -39,6 +123,32 @@ body {
--button-text-color: #ffffff; --button-text-color: #ffffff;
--button-text-color_disabled: #cccccc; --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 { hr {
@ -46,64 +156,6 @@ hr {
border-top: 1px solid #BDBDBD; 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 { .country-selector {
opacity: 1 !important; opacity: 1 !important;
display: none !important; display: none !important;
@ -117,7 +169,6 @@ hr {
color: var(--text-color); color: var(--text-color);
} }
qr-code canvas { qr-code canvas {
transition: all 0.3s ease 0s; transition: all 0.3s ease 0s;
} }
@ -175,5 +226,21 @@ input::-webkit-date-and-time-value {
text-align: left; 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%; } html, body { height: 100%; }
body { margin: 0; } body { margin: 0; }

View File

@ -7,20 +7,8 @@ import {
platformBrowserDynamicTesting platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing'; } 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. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), 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, "experimentalDecorators": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "es2020", "target": "ES2022",
"module": "es2020", "module": "es2020",
"lib": [ "lib": [
"es2020", "es2020",
@ -24,6 +24,7 @@
], ],
"resolveJsonModule": true, "resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"useDefineForClassFields": false
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,