dev #13995
изменил структуру под аб тестирование, сделал 2 различных вариант (один дефолтный, другой с красным текстом) модули загружаются лениво, для оптимизации загрузки приложения пофиксил подсчет суммы транзакций за периоды
This commit is contained in:
parent
04ecbb837c
commit
86ac20f5e5
@ -8,13 +8,14 @@ import { AuthGuard } from './guards/auth-guard.guard';
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: GuestCardComponent,
|
loadChildren: () => null as any,
|
||||||
canActivate: [AuthGuard]
|
// component: GuestCardComponent,
|
||||||
|
// canActivate: [AuthGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: LoginComponent
|
component: LoginComponent
|
||||||
}
|
},
|
||||||
// { path: '**', component: NotFoundComponent }
|
// { path: '**', component: NotFoundComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,60 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { RouteConfigLoadStart, Router } from '@angular/router';
|
||||||
|
import { CookiesService } from './services/cookies.service';
|
||||||
|
import { JsonrpcService, RpcService } from './services/jsonrpc.service';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.scss']
|
styleUrls: ['./app.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit {
|
||||||
title = 'Coffee Like';
|
title = 'Coffee Like';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private cookiesService: CookiesService,
|
||||||
|
private jsonRpcService: JsonrpcService
|
||||||
|
) {
|
||||||
|
this.router.events.subscribe((x) => {
|
||||||
|
if (x instanceof RouteConfigLoadStart && x.route.path === '') {
|
||||||
|
x.route.loadChildren = () => {
|
||||||
|
switch (this.cookiesService.getItem('presentation-option')) {
|
||||||
|
case 'first':
|
||||||
|
return import(
|
||||||
|
'./presentation-options/first-option/first-option.module'
|
||||||
|
).then((mod) => mod.FirstOptionModule);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return import(
|
||||||
|
'./presentation-options/default-option/default-option.module'
|
||||||
|
).then((mod) => mod.DefaultOptionModule);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdditionalInfo() {
|
||||||
|
return this.jsonRpcService.rpc(
|
||||||
|
{
|
||||||
|
method: 'getAdditionalInfo',
|
||||||
|
params: [],
|
||||||
|
},
|
||||||
|
RpcService.authService,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getAdditionalInfo().subscribe({
|
||||||
|
next: (value) => {
|
||||||
|
this.cookiesService.setCookie('presentation-option', value?.data?.interface_id || 'default')
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@ import {
|
|||||||
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';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -78,7 +79,7 @@ import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
|||||||
FooterComponent,
|
FooterComponent,
|
||||||
SocialMediaButtonsComponent,
|
SocialMediaButtonsComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
FocusNextInputDirective,
|
// FocusNextInputDirective,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -110,7 +111,8 @@ import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
|||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
MatBottomSheetModule,
|
MatBottomSheetModule,
|
||||||
MatProgressSpinnerModule
|
MatProgressSpinnerModule,
|
||||||
|
DirectivesModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DialogService,
|
DialogService,
|
||||||
|
|||||||
12
angular/src/app/directives/directives.module.ts
Normal file
12
angular/src/app/directives/directives.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FocusNextInputDirective } from './focus-next-input.directive';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule
|
||||||
|
],
|
||||||
|
declarations: [FocusNextInputDirective],
|
||||||
|
exports: [FocusNextInputDirective]
|
||||||
|
})
|
||||||
|
export class DirectivesModule { }
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { DownloadAppDirective } from './download-app.directive';
|
|
||||||
|
|
||||||
describe('DownloadAppDirective', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
const directive = new DownloadAppDirective();
|
|
||||||
expect(directive).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { FocusNextInputDirective } from './focus-next-input.directive';
|
|
||||||
|
|
||||||
describe('FocusNextInputDirective', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
const directive = new FocusNextInputDirective();
|
|
||||||
expect(directive).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -6,6 +6,8 @@ export enum PageCode {
|
|||||||
UserData,
|
UserData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Moment extends moment.Moment {}
|
||||||
|
|
||||||
export interface Page {
|
export interface Page {
|
||||||
code: PageCode;
|
code: PageCode;
|
||||||
component?: any;
|
component?: any;
|
||||||
|
|||||||
@ -166,7 +166,7 @@ export class AccountComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteToken(): void {
|
deleteToken(): void {
|
||||||
this.cookiesService.deleteCookie('token');
|
this.cookiesService.logout();
|
||||||
// this.router.navigate(['.'], {
|
// this.router.navigate(['.'], {
|
||||||
// queryParams: {},
|
// queryParams: {},
|
||||||
// });
|
// });
|
||||||
|
|||||||
@ -124,6 +124,7 @@
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-ms-thumb {
|
&::-ms-thumb {
|
||||||
|
|||||||
@ -41,17 +41,17 @@ export class GuestCardComponent implements OnInit {
|
|||||||
$loading: true,
|
$loading: true,
|
||||||
get currentAmount():number {
|
get currentAmount():number {
|
||||||
const amount = this.currentPurchases.reduce((accumulator, currentValue) => {
|
const amount = this.currentPurchases.reduce((accumulator, currentValue) => {
|
||||||
if (currentValue.transactionType !== 'PayFromWallet') return 0;
|
if (currentValue.transactionType !== 'PayFromWallet') return accumulator;
|
||||||
return accumulator + currentValue.transactionSum;
|
return accumulator + currentValue.orderSum!;
|
||||||
}, 0);
|
}, 0);
|
||||||
return amount * -1
|
return amount * 1
|
||||||
},
|
},
|
||||||
get lastAmount():number {
|
get lastAmount():number {
|
||||||
const amount = this.lastPurchases.reduce((accumulator, currentValue) => {
|
const amount = this.lastPurchases.reduce((accumulator, currentValue) => {
|
||||||
if (currentValue.transactionType !== 'PayFromWallet') return 0;
|
if (currentValue.transactionType !== 'PayFromWallet') return accumulator;
|
||||||
return accumulator + currentValue.transactionSum;
|
return accumulator + currentValue.orderSum!;
|
||||||
}, 0);
|
}, 0);
|
||||||
return amount * -1
|
return amount * 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,12 +85,14 @@ export class GuestCardComponent implements OnInit {
|
|||||||
'PayFromWallet',
|
'PayFromWallet',
|
||||||
'CancelPayFromWallet',
|
'CancelPayFromWallet',
|
||||||
'RefillWallet',
|
'RefillWallet',
|
||||||
|
'RefillWalletFromOrder'
|
||||||
].includes(purchase.transactionType || '')
|
].includes(purchase.transactionType || '')
|
||||||
);
|
);
|
||||||
this.lastPurchase = value[this.customerInfo?.id].filter(
|
this.lastPurchase = this.purchases.filter(
|
||||||
(purchase: Purchase) =>
|
(purchase: Purchase) =>
|
||||||
[
|
[
|
||||||
'PayFromWallet',
|
'PayFromWallet',
|
||||||
|
'RefillWalletFromOrder'
|
||||||
].includes(purchase.transactionType || '')
|
].includes(purchase.transactionType || '')
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ export class GuestCardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteToken(): void {
|
deleteToken(): void {
|
||||||
this.cookiesService.deleteCookie('token');
|
this.cookiesService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
<div class="tab">
|
||||||
|
<input [id]="'tab-' + guid" type="checkbox" name="tabs" />
|
||||||
|
<label [for]="'tab-' + guid">{{ header }}</label>
|
||||||
|
<div class="tab-content">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: solid 1px #bdbdbd;
|
||||||
|
}
|
||||||
|
.tab input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.tab label {
|
||||||
|
display: block;
|
||||||
|
padding: 20px 26px;
|
||||||
|
position: relative;
|
||||||
|
background: #ffffff00;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
// .tab label:before {
|
||||||
|
// display: block;
|
||||||
|
// content: " ";
|
||||||
|
// padding-top: 5px;
|
||||||
|
// }
|
||||||
|
.tab-content {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #292929;
|
||||||
|
-webkit-transition: all 0.35s;
|
||||||
|
-o-transition: all 0.35s;
|
||||||
|
transition: all 0.35s;
|
||||||
|
border-top: solid 1px #bdbdbd;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tab-content p {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
/* :checked */
|
||||||
|
.tab input:checked ~ .tab-content {
|
||||||
|
padding: 16px 26px;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon */
|
||||||
|
.tab label::after {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
line-height: 3;
|
||||||
|
text-align: center;
|
||||||
|
-webkit-transition: all 0.35s;
|
||||||
|
-o-transition: all 0.35s;
|
||||||
|
transition: all 0.35s;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.tab input[type="checkbox"] + label::after {
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
.tab input[type="radio"] + label::after {
|
||||||
|
content: "\25BC";
|
||||||
|
}
|
||||||
|
.tab input[type="checkbox"]:checked + label::after {
|
||||||
|
transform: rotate(315deg);
|
||||||
|
}
|
||||||
|
.tab input[type="radio"]:checked + label::after {
|
||||||
|
transform: rotateX(180deg);
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AccordionComponent } from './accordion.component';
|
||||||
|
|
||||||
|
describe('AccordionComponent', () => {
|
||||||
|
let component: AccordionComponent;
|
||||||
|
let fixture: ComponentFixture<AccordionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AccordionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AccordionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
export interface IAccordionData {
|
||||||
|
header: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-accordion[header]',
|
||||||
|
templateUrl: './accordion.component.html',
|
||||||
|
styleUrls: ['./accordion.component.scss']
|
||||||
|
})
|
||||||
|
export class AccordionComponent implements OnInit {
|
||||||
|
@Input() header!: string
|
||||||
|
public guid: string = uuidv4()
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
<img src="/assets/logo.svg" alt="Логотип">
|
||||||
|
<h3>Горячая линия</h3>
|
||||||
|
<a class="phone-number" href="tel:88003334130">8 (800) 333-41-30</a>
|
||||||
|
<app-social-media-buttons></app-social-media-buttons>
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.24px;
|
||||||
|
color: #6a737c;
|
||||||
|
}
|
||||||
|
.phone-number {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FooterComponent } from './footer.component';
|
||||||
|
|
||||||
|
describe('FooterComponent', () => {
|
||||||
|
let component: FooterComponent;
|
||||||
|
let fixture: ComponentFixture<FooterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ FooterComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(FooterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-footer',
|
||||||
|
templateUrl: './footer.component.html',
|
||||||
|
styleUrls: ['./footer.component.scss']
|
||||||
|
})
|
||||||
|
export class FooterComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
<router-outlet></router-outlet>
|
||||||
|
<app-footer></app-footer>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { IndexComponent } from './index.component';
|
||||||
|
|
||||||
|
describe('IndexComponent', () => {
|
||||||
|
let component: IndexComponent;
|
||||||
|
let fixture: ComponentFixture<IndexComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ IndexComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(IndexComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-index',
|
||||||
|
templateUrl: './index.component.html',
|
||||||
|
styleUrls: ['./index.component.scss']
|
||||||
|
})
|
||||||
|
export class IndexComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<h2>Пригласи друзей!</h2>
|
||||||
|
<div class="container">
|
||||||
|
<p>
|
||||||
|
Пригласи друзей зарегистрироваться в приложении по твоему уникальному коду и
|
||||||
|
получи бонусы, когда они совершат первую покупку.
|
||||||
|
</p>
|
||||||
|
<button class="share" (click)="share()" [disabled]="loading">
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<mat-icon
|
||||||
|
*ngIf="!loading"
|
||||||
|
aria-hidden="false"
|
||||||
|
aria-label="Share"
|
||||||
|
fontIcon="share"
|
||||||
|
style="color: #fff"
|
||||||
|
></mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #spinner let-diameter>
|
||||||
|
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
:host {
|
||||||
|
padding: 16px;
|
||||||
|
h2 {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share {
|
||||||
|
width: 72px;
|
||||||
|
height: 48px;
|
||||||
|
background: #28af49;
|
||||||
|
padding: 8px 26px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { InviteFriendsComponent } from './invite-friends.component';
|
||||||
|
|
||||||
|
describe('InviteFriendsComponent', () => {
|
||||||
|
let component: InviteFriendsComponent;
|
||||||
|
let fixture: ComponentFixture<InviteFriendsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ InviteFriendsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(InviteFriendsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-invite-friends',
|
||||||
|
templateUrl: './invite-friends.component.html',
|
||||||
|
styleUrls: ['./invite-friends.component.scss']
|
||||||
|
})
|
||||||
|
export class InviteFriendsComponent implements OnInit {
|
||||||
|
public refUrl: string = `${environment.defaultUrl}/?refUserId=`
|
||||||
|
public loading: boolean = true;
|
||||||
|
private shareData: ShareData = {
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private jsonrpc: JsonrpcService,
|
||||||
|
private messageService: MessageService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
const accountData = (await lastValueFrom(
|
||||||
|
this.jsonrpc
|
||||||
|
.rpc(
|
||||||
|
{
|
||||||
|
method: 'getTokenData',
|
||||||
|
params: [],
|
||||||
|
},
|
||||||
|
RpcService.authService,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)).data
|
||||||
|
|
||||||
|
this.refUrl += accountData.user_id
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
if (navigator.share) {
|
||||||
|
navigator.share({
|
||||||
|
title: document.title,
|
||||||
|
text: "Coffee Like",
|
||||||
|
url: this.refUrl
|
||||||
|
})
|
||||||
|
.then(() => console.log('Successful share'))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error sharing:', error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<h2>Ваш предыдущий заказ</h2>
|
||||||
|
<div class="info-order">
|
||||||
|
<p class="flex"><span>Дата: </span>
|
||||||
|
<span *ngIf="!loading">{{(lastOrder?.transactionCreateDate | date:'dd.MM.yyyyг.') || 'Данные не найдены'}}</span>
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</p>
|
||||||
|
<p class="flex"><span>На сумму: </span>
|
||||||
|
<span *ngIf="!loading">{{lastOrder?.orderSum ? lastOrder?.orderSum + ' ₽' : 'Данные не найдены'}}</span>
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="https://yandex.ru/profile/151770398186" target="_blank">
|
||||||
|
<button class="evaluate-order">Оценить заказ</button>
|
||||||
|
</a>
|
||||||
|
<p class="info">
|
||||||
|
Списание бонусов возможно на любые категории. Бонусами можно оплатить 100%
|
||||||
|
суммы покупки. Бонусы начисляются только на напитки с учётом добавок.
|
||||||
|
Неиспользованные бонусы сгорают в течение 90 дней.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ng-template #spinner let-diameter>
|
||||||
|
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
:host {
|
||||||
|
padding: 24px 16px 56px;
|
||||||
|
|
||||||
|
& > h2 {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .info-order {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
span {
|
||||||
|
color: #828282;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.evaluate-order {
|
||||||
|
margin: 24px 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
border: 2px solid #28af49;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #28af49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LastOrderComponent } from './last-order.component';
|
||||||
|
|
||||||
|
describe('LastOrderComponent', () => {
|
||||||
|
let component: LastOrderComponent;
|
||||||
|
let fixture: ComponentFixture<LastOrderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ LastOrderComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LastOrderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Purchase } from 'src/app/interface/data';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-last-order[lastOrder]',
|
||||||
|
templateUrl: './last-order.component.html',
|
||||||
|
styleUrls: ['./last-order.component.scss']
|
||||||
|
})
|
||||||
|
export class LastOrderComponent implements OnInit {
|
||||||
|
@Input() lastOrder!: Purchase;
|
||||||
|
@Input() loading!: boolean;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<div class="container">
|
||||||
|
<mat-icon style="cursor: pointer;" aria-hidden="false" aria-label="Назад" fontIcon="arrow_back_ios" class="back-arrow" (click)="back()"></mat-icon>
|
||||||
|
<h1 class="title">{{title}}</h1>
|
||||||
|
<div class="plug"></div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px 16px;
|
||||||
|
width: 100%;
|
||||||
|
background: #231f20;
|
||||||
|
box-shadow: 0px 8px 16px rgba(17, 17, 17, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.back-arrow {
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.plug {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-family: "Gotham Pro", sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NavbarComponent } from './navbar.component';
|
||||||
|
|
||||||
|
describe('NavbarComponent', () => {
|
||||||
|
let component: NavbarComponent;
|
||||||
|
let fixture: ComponentFixture<NavbarComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ NavbarComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NavbarComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navbar[title]',
|
||||||
|
templateUrl: './navbar.component.html',
|
||||||
|
styleUrls: ['./navbar.component.scss']
|
||||||
|
})
|
||||||
|
export class NavbarComponent implements OnInit {
|
||||||
|
@Input() title: string = 'Название не задано'
|
||||||
|
@Output() backEvent = new EventEmitter<null>();
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
back() {
|
||||||
|
this.backEvent.emit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<a *ngFor="let link of links" [href]="link.url" target="_blank">
|
||||||
|
<img [src]="link.imgUrl" [alt]="link.label" />
|
||||||
|
</a>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: space-between;
|
||||||
|
a {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: #333333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SocialMediaButtonsComponent } from './social-media-buttons.component';
|
||||||
|
|
||||||
|
describe('SocialMediaButtonsComponent', () => {
|
||||||
|
let component: SocialMediaButtonsComponent;
|
||||||
|
let fixture: ComponentFixture<SocialMediaButtonsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SocialMediaButtonsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SocialMediaButtonsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
export interface ISocialMediaLink {
|
||||||
|
url: string;
|
||||||
|
imgUrl: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-social-media-buttons',
|
||||||
|
templateUrl: './social-media-buttons.component.html',
|
||||||
|
styleUrls: ['./social-media-buttons.component.scss']
|
||||||
|
})
|
||||||
|
export class SocialMediaButtonsComponent implements OnInit {
|
||||||
|
public links: ISocialMediaLink[] = [
|
||||||
|
{
|
||||||
|
label: 'Инстаграм',
|
||||||
|
url: 'https://www.instagram.com/',
|
||||||
|
imgUrl: '/assets/social-media-icons/instagram.svg'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ВК',
|
||||||
|
url: 'https://vk.com/coffeelike_com',
|
||||||
|
imgUrl: '/assets/social-media-icons/vk.svg'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Youtube',
|
||||||
|
url: 'https://www.youtube.com/c/coffeelikeru',
|
||||||
|
imgUrl: '/assets/social-media-icons/youtube.svg'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { IndexComponent } from './components/index/index.component';
|
||||||
|
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
|
||||||
|
import { AuthGuard } from 'src/app/guards/auth-guard.guard';
|
||||||
|
import { LoginComponent } from './pages/login/login.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: GuestCardComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
component: LoginComponent
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class DefaultOptionRoutingModule {}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { DefaultOptionRoutingModule } from './default-option-routing.module';
|
||||||
|
import { IndexComponent } from './components/index/index.component';
|
||||||
|
import { FooterComponent } from './components/footer/footer.component';
|
||||||
|
import { SocialMediaButtonsComponent } from './components/social-media-buttons/social-media-buttons.component';
|
||||||
|
import { NavbarComponent } from './components/navbar/navbar.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
|
||||||
|
import { QrCodeModule } from 'ng-qrcode';
|
||||||
|
import { AccordionComponent } from './components/accordion/accordion.component';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { LastOrderComponent } from './components/last-order/last-order.component';
|
||||||
|
import { InviteFriendsComponent } from './components/invite-friends/invite-friends.component';
|
||||||
|
import { LoginComponent } from './pages/login/login.component';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { NgxMatIntlTelInputComponent } from 'ngx-mat-intl-tel-input';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { DirectivesModule } from 'src/app/directives/directives.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
IndexComponent,
|
||||||
|
FooterComponent,
|
||||||
|
SocialMediaButtonsComponent,
|
||||||
|
NavbarComponent,
|
||||||
|
GuestCardComponent,
|
||||||
|
AccordionComponent,
|
||||||
|
LastOrderComponent,
|
||||||
|
InviteFriendsComponent,
|
||||||
|
LoginComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
DefaultOptionRoutingModule,
|
||||||
|
MatIconModule,
|
||||||
|
QrCodeModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
NgxMatIntlTelInputComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
DirectivesModule
|
||||||
|
],
|
||||||
|
bootstrap: [IndexComponent],
|
||||||
|
})
|
||||||
|
export class DefaultOptionModule {}
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
<app-navbar title="Карта гостя" (backEvent)="logout()"></app-navbar>
|
||||||
|
|
||||||
|
<div class="guest-card">
|
||||||
|
<div class="guest-card__qr" (click)="qrCodeClick()">
|
||||||
|
<ng-container *ngIf="customerInfo || cookiesService.getItem('phone-number')">
|
||||||
|
<qr-code
|
||||||
|
[value]="cookiesService.getItem('phone-number') || customerInfo?.phone.substr(2) || 'Данные не найдены'"
|
||||||
|
[margin]="0"
|
||||||
|
[size]="qrCodeSize"
|
||||||
|
errorCorrectionLevel="M"
|
||||||
|
></qr-code>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!cookiesService.getItem('phone-number') && !customerInfo">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 85 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="guest-card__user-description">
|
||||||
|
Текущий баланс бонусов:
|
||||||
|
<span *ngIf="customerInfo">
|
||||||
|
{{ loyaltyProgram.getBalanceAmount(customerInfo?.walletBalances) }}
|
||||||
|
бонусов</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<span id="bonuses-condition"></span>
|
||||||
|
<app-accordion header="Условия начисления бонусов">
|
||||||
|
<p>
|
||||||
|
Расчет начисления бонусов - 10% от суммы покупок за период с 11.01.2023г.
|
||||||
|
по 31.03.2023 г.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
За период с 11.01.2023г. по 31.03.2023 г. сумма ваших покупок составила
|
||||||
|
3700 руб.
|
||||||
|
</p>
|
||||||
|
<p>Начисляемый бонус 10% от суммы покупок</p>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
Возврат покупки, за которую бонусы были начислены:
|
||||||
|
<li>
|
||||||
|
В случае, если бонусов на счету достаточно для списания, бонусы
|
||||||
|
списываются в полном ранее начисленном за возвращаемый товар объеме.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
В случае, если бонусов на счету недостаточно, формируется минусовой
|
||||||
|
баланс.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
Возврат покупки, которая была оплачена бонусами:
|
||||||
|
<li>
|
||||||
|
В случае предъявления Участником кассового или товарного чека, сумма
|
||||||
|
бонусов, списанная для оплаты возвращаемого товара, зачисляется на счет
|
||||||
|
участника.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
В случае возврата товара с применением оплаты бонусами, клиенту
|
||||||
|
возвращается денежная сумма в размере, внесенном Участником в оплату
|
||||||
|
товара при покупке, за вычетом суммы, оплаченной бонусами.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</app-accordion>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="guest-card__purchases-description"
|
||||||
|
[ngStyle]="{
|
||||||
|
display: loyaltyProgram.purchaseData.$loading ? 'grid' : 'block'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Сумма ваших покупок за период с
|
||||||
|
{{ loyaltyProgram.purchaseData.currentPeriod[0].format("DD.MM.yyyyг.") }} -
|
||||||
|
<span *ngIf="!loyaltyProgram.purchaseData.$loading"
|
||||||
|
>{{ loyaltyProgram.purchaseData.currentAmount }} руб.</span
|
||||||
|
>
|
||||||
|
<ng-container *ngIf="loyaltyProgram.purchaseData.$loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<app-accordion header="Узнать % начисляемых бонусов">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="guest-card__level-info">
|
||||||
|
<ng-container *ngIf="!loyaltyProgram.purchaseData.$loading">
|
||||||
|
<ng-container *ngIf="currentLvlPeriod.end">
|
||||||
|
<h2>
|
||||||
|
До следующего уровня за период с
|
||||||
|
{{ loyaltyProgram.purchaseData.currentPeriod[0].format("DD.MM.yyyyг") }} по
|
||||||
|
{{ loyaltyProgram.purchaseData.currentPeriod[1].format("DD.MM.yyyyг") }}
|
||||||
|
осталось совершить покупки на {{ currentLvlPeriod.end - (loyaltyProgram.purchaseData.currentAmount || 0) + 1 }} рублей
|
||||||
|
</h2>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
[value]="loyaltyProgram.purchaseData.currentAmount"
|
||||||
|
[min]="currentLvlPeriod.start"
|
||||||
|
[max]="currentLvlPeriod.end"
|
||||||
|
[step]="0.01"
|
||||||
|
disabled="true"
|
||||||
|
[ngStyle]="{
|
||||||
|
'background-size': ((loyaltyProgram.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="loyaltyProgram.purchaseData.$loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 48 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<p class="show-more">
|
||||||
|
<a href="#bonuses-condition">
|
||||||
|
Узнать условия начисления бонусов
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<app-last-order
|
||||||
|
[lastOrder]="lastPurchase"
|
||||||
|
[loading]="loyaltyProgram.purchaseData.$loading"
|
||||||
|
></app-last-order>
|
||||||
|
<hr />
|
||||||
|
<app-invite-friends></app-invite-friends>
|
||||||
|
<hr />
|
||||||
|
<div class="guest-card__download-app">
|
||||||
|
<img src="/assets/download-app.svg" alt="Скачай приложение" />
|
||||||
|
</div>
|
||||||
|
<a class="guest-card__loyalty-program" routerLink="loyalty-program"
|
||||||
|
>Подробнее о правилах <br />
|
||||||
|
Программы лояльности</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #spinner let-diameter>
|
||||||
|
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
:host {
|
||||||
|
.guest-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0 0;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
&__qr {
|
||||||
|
padding: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
#fff 33%,
|
||||||
|
transparent 0px,
|
||||||
|
transparent 67%,
|
||||||
|
#fff 0px
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
#ffe 33%,
|
||||||
|
transparent 0px,
|
||||||
|
transparent 66%,
|
||||||
|
#fff 0px
|
||||||
|
),
|
||||||
|
linear-gradient(#fff 33%, transparent 0px, transparent 67%, #fff 0),
|
||||||
|
linear-gradient(90deg, #fff 33%, transparent 0, transparent 66%, #fff 0);
|
||||||
|
background-size: 1px 100%, 100% 1px, 1px 100%, 100% 1px;
|
||||||
|
background-position: 0 0, 0 0, 100% 100%, 100% 100%;
|
||||||
|
background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__user-description {
|
||||||
|
margin: 18px 0 0;
|
||||||
|
padding: 14px 24px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-top: 1px solid #fff;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
// font-family: "Goldman";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 19px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #f2994a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__purchases-description {
|
||||||
|
margin: 0;
|
||||||
|
padding: 14px 24px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
// font-family: "Goldman";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 19px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
grid-template-columns: calc(100% - 24px) 24px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #219653;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__level-info {
|
||||||
|
padding: 36px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
background: #231f20;
|
||||||
|
box-shadow: 0px 0px 3px #f2c94c59;
|
||||||
|
background-image: linear-gradient(#f2c94c, #f2c94c);
|
||||||
|
background-size: 70% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #f2c94c;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background-color: #f2c94c;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: #f2c94c;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f2c94c;
|
||||||
|
cursor: ew-resize;
|
||||||
|
box-shadow: 0 0 2px 0 #555;
|
||||||
|
transition: background 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f2c94c;
|
||||||
|
cursor: ew-resize;
|
||||||
|
box-shadow: 0 0 2px 0 #555;
|
||||||
|
transition: background 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .show-more {
|
||||||
|
margin-top: 42px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #28af49;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__download-app {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 32px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: calc(100% - 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loyalty-program {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 17px 0 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app-accordion {
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
list-style-type: disc;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GuestCardComponent } from './guest-card.component';
|
||||||
|
|
||||||
|
describe('GuestCardComponent', () => {
|
||||||
|
let component: GuestCardComponent;
|
||||||
|
let fixture: ComponentFixture<GuestCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ GuestCardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GuestCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ExitComponent } from 'src/app/components/exit/exit.component';
|
||||||
|
import { CookiesService } from 'src/app/services/cookies.service';
|
||||||
|
import { WpJsonService } from 'src/app/services/wp-json.service';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Moment, Purchase, lvlPeriod } from 'src/app/interface/data';
|
||||||
|
import { lvlPeriods } from 'src/app/app.constants';
|
||||||
|
import { LoyaltyProgramService } from 'src/app/services/loyalty-program.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-guest-card',
|
||||||
|
templateUrl: './guest-card.component.html',
|
||||||
|
styleUrls: ['./guest-card.component.scss'],
|
||||||
|
})
|
||||||
|
export class GuestCardComponent implements OnInit {
|
||||||
|
public qrCodeSize: number = 85;
|
||||||
|
private isQrCodeClicked: boolean = false;
|
||||||
|
public customerInfo!: any;
|
||||||
|
public purchases!: Purchase[];
|
||||||
|
public lastPurchase!: Purchase;
|
||||||
|
|
||||||
|
public lvlPeriods: lvlPeriod[] = lvlPeriods;
|
||||||
|
public currentLvlPeriod!: lvlPeriod;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _bottomSheet: MatBottomSheet,
|
||||||
|
public cookiesService: CookiesService,
|
||||||
|
private router: Router,
|
||||||
|
private wpJsonService: WpJsonService,
|
||||||
|
public loyaltyProgram: LoyaltyProgramService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const token = this.cookiesService.getItem('token');
|
||||||
|
this.wpJsonService
|
||||||
|
.getCustomerInfo(
|
||||||
|
environment.systemId,
|
||||||
|
token || '',
|
||||||
|
environment.icardProxy
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (value) => {
|
||||||
|
this.customerInfo = value.customer_info;
|
||||||
|
this.cookiesService.setCookie('phone-number', this.customerInfo.phone.substr(2))
|
||||||
|
this.getPurchases().subscribe((value) => {
|
||||||
|
this.purchases = this.loyaltyProgram.filterPurchases(value[this.customerInfo?.id])
|
||||||
|
this.lastPurchase = this.loyaltyProgram.getLastPurchase(this.purchases)
|
||||||
|
|
||||||
|
this.loyaltyProgram.setLastPurchases(this.purchases)
|
||||||
|
this.loyaltyProgram.setCurrentPurchases(this.purchases)
|
||||||
|
|
||||||
|
const currentAmount = this.loyaltyProgram.purchaseData.currentAmount || 0
|
||||||
|
this.currentLvlPeriod = this.lvlPeriods.find((item) => item.start <= currentAmount && currentAmount <= (item.end || Infinity))!
|
||||||
|
this.loyaltyProgram.purchaseData.$loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
qrCodeClick() {
|
||||||
|
this.isQrCodeClicked = !this.isQrCodeClicked;
|
||||||
|
this.qrCodeSize = this.isQrCodeClicked ? 180 : 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteToken(): void {
|
||||||
|
this.cookiesService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
const bottomSheet = this._bottomSheet.open(ExitComponent);
|
||||||
|
bottomSheet.afterDismissed().subscribe({
|
||||||
|
next: (val) => {
|
||||||
|
if (val) {
|
||||||
|
this.deleteToken();
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPurchases(
|
||||||
|
start: Date | Moment = moment().subtract(1, 'months').startOf('month'),
|
||||||
|
end: Date | Moment = moment()
|
||||||
|
): Observable<any> {
|
||||||
|
const token = this.cookiesService.getItem('token');
|
||||||
|
const delta = moment(end).diff(moment(start), 'days');
|
||||||
|
return this.wpJsonService.getTransactions(
|
||||||
|
environment.systemId,
|
||||||
|
token ?? '',
|
||||||
|
environment.icardProxy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
<app-navbar
|
||||||
|
*ngIf="phoneForm.value.phone && !isShowNumber"
|
||||||
|
[title]="phoneForm.value.phone"
|
||||||
|
(backEvent)="backToPhoneForm()"
|
||||||
|
[ngStyle]="{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0
|
||||||
|
}"
|
||||||
|
></app-navbar>
|
||||||
|
<h1>Участвуй в программе лояльности COFFEE LIKE</h1>
|
||||||
|
<p class="description">Начни получать бонусы прямо сейчас</p>
|
||||||
|
<form
|
||||||
|
*ngIf="isShowNumber; else smsCode"
|
||||||
|
(ngSubmit)="submitNumber()"
|
||||||
|
[formGroup]="phoneForm"
|
||||||
|
>
|
||||||
|
<!-- <div class="input-container"> -->
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Ваше имя</mat-label>
|
||||||
|
<input formControlName="name" matInput type="text" />
|
||||||
|
</mat-form-field>
|
||||||
|
<!-- </div> -->
|
||||||
|
<div class="input-container">
|
||||||
|
<!-- <label for="number">Номер телефона</label>
|
||||||
|
<input id="number" type="text" placeholder="Введите номер" /> -->
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Номер телефона</mat-label>
|
||||||
|
<ngx-mat-intl-tel-input
|
||||||
|
formControlName="phone"
|
||||||
|
[enablePlaceholder]="true"
|
||||||
|
[enableSearch]="false"
|
||||||
|
[onlyCountries]="['ru', 'kg', 'by', 'kz', 'fi', 'de']"
|
||||||
|
[preferredCountries]="['ru']"
|
||||||
|
name="phone"
|
||||||
|
#phone
|
||||||
|
>
|
||||||
|
</ngx-mat-intl-tel-input>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<p class="offer">
|
||||||
|
Используя приложение, вы принимаете условия в <span>соглашениях</span> и
|
||||||
|
соглашаетесь на получение рекламно-информационных сообщений
|
||||||
|
</p>
|
||||||
|
<button [disabled]="phoneForm.invalid">Принять участие</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ng-template #smsCode>
|
||||||
|
<h2>Введите код из SMS</h2>
|
||||||
|
<form class="code-form" [formGroup]="codeForm" (ngSubmit)="submitCode()">
|
||||||
|
<div class="inputs-container">
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field1"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field1
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code1"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field2"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field2
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code2"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field3"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field3
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code3"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
</div>
|
||||||
|
<button>Войти</button>
|
||||||
|
</form>
|
||||||
|
<p class="resend-code">
|
||||||
|
Не пришло SMS?<br />
|
||||||
|
<ng-container *ngIf="timeLeft">
|
||||||
|
Отправим повторно через {{timeLeft}}с
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!timeLeft">
|
||||||
|
<span class="resend" (click)="submitNumber()">Отправить повторно</span>
|
||||||
|
</ng-container>
|
||||||
|
</p>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,214 @@
|
|||||||
|
:host {
|
||||||
|
padding-top: 48px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 52px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 302px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 44px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
width: 180px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 35px;
|
||||||
|
|
||||||
|
.offer {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-family: "Gowun Dodum";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 17px;
|
||||||
|
text-align: center;
|
||||||
|
span {
|
||||||
|
color: #13a538;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6a737c;
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px 16px 8px;
|
||||||
|
background-color: #252323;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: none;
|
||||||
|
border-top: solid #6a737c 1px;
|
||||||
|
border-bottom: solid #6a737c 1px;
|
||||||
|
color: #6a737c;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 46px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
padding: 10px 24px;
|
||||||
|
background: #28af49;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
&:disabled {
|
||||||
|
background-color: #344a3a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-form {
|
||||||
|
width: 100%;
|
||||||
|
// input {
|
||||||
|
// width: 100%;
|
||||||
|
// padding: 24px 16px 8px;
|
||||||
|
// background-color: #252323;
|
||||||
|
// margin-bottom: 16px;
|
||||||
|
// border: none;
|
||||||
|
// border-top: solid #6a737c 1px;
|
||||||
|
// border-bottom: solid #6a737c 1px;
|
||||||
|
// color: #6a737c;
|
||||||
|
// font-style: normal;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 22px;
|
||||||
|
// line-height: 28px;
|
||||||
|
// }
|
||||||
|
.inputs-container {
|
||||||
|
width: 102px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
// code
|
||||||
|
.box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
border-radius: 6px;
|
||||||
|
// box-shadow: 0 0 6px 1px hsla(240, 54%, 61%, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.box:focus-within {
|
||||||
|
box-shadow: 0 0 6px 1px rgba($color: #28af49, $alpha: 0.2),
|
||||||
|
0 0 0 2px rgba($color: #28af49, $alpha: 0.6);
|
||||||
|
}
|
||||||
|
.box::before,
|
||||||
|
.box::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.box::before {
|
||||||
|
// background: hsl(240, 54%, 97%);
|
||||||
|
z-index: 1;
|
||||||
|
transition: background-color 450ms cubic-bezier(0.25, 0.01, 0.25, 1);
|
||||||
|
}
|
||||||
|
.box::after {
|
||||||
|
transform: translateY(100%);
|
||||||
|
background-color: hsl(145, 0%, 42%);
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 10;
|
||||||
|
transition: transform 450ms cubic-bezier(0.25, 0.01, 0.25, 1),
|
||||||
|
opacity 450ms cubic-bezier(0.25, 0.01, 0.25, 1),
|
||||||
|
background-color 450ms cubic-bezier(0.25, 0.01, 0.25, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
position: relative;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 25.21px;
|
||||||
|
line-height: 42px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.field::placeholder {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resend-code {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 23px;
|
||||||
|
.resend {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
describe('LoginComponent', () => {
|
||||||
|
let component: LoginComponent;
|
||||||
|
let fixture: ComponentFixture<LoginComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ LoginComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LoginComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
OnInit
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { CookiesService } from 'src/app/services/cookies.service';
|
||||||
|
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.scss'],
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit, AfterViewInit {
|
||||||
|
public isShowNumber: boolean = true;
|
||||||
|
public phoneForm = new FormGroup({
|
||||||
|
name: new FormControl('', []),
|
||||||
|
phone: new FormControl('', [Validators.required]),
|
||||||
|
});
|
||||||
|
public codeForm = new FormGroup({
|
||||||
|
code: new FormControl('', [Validators.required]),
|
||||||
|
code1: new FormControl('', [Validators.required]),
|
||||||
|
code2: new FormControl('', [Validators.required]),
|
||||||
|
code3: new FormControl('', [Validators.required]),
|
||||||
|
});
|
||||||
|
private inputIds = ['field', 'field1', 'field2', 'field3'];
|
||||||
|
timeLeft: number = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cookiesService: CookiesService,
|
||||||
|
private router: Router,
|
||||||
|
private jsonrpc: JsonrpcService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private _snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
public inputFocusEmitter = new EventEmitter<string>();
|
||||||
|
|
||||||
|
@HostListener('window:keyup', ['$event'])
|
||||||
|
HandlKeyEvents(event: any) {
|
||||||
|
if (!event.target.classList.contains('field')) return;
|
||||||
|
const key = event.key.toLocaleLowerCase();
|
||||||
|
let elementId = '';
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'backspace':
|
||||||
|
elementId = event.target.id;
|
||||||
|
event.target.value = '';
|
||||||
|
const prevInputIndex = this.inputIds.indexOf(elementId) - 1;
|
||||||
|
if (prevInputIndex >= 0) {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[prevInputIndex]}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
elementId = event.target.id;
|
||||||
|
const index = this.inputIds.indexOf(elementId);
|
||||||
|
const nextInputIndex = index + 1;
|
||||||
|
if (event.target.value.length > 1) {
|
||||||
|
event.target.value = event.target.value.slice(-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (nextInputIndex > 0 && nextInputIndex <= this.inputIds.length) {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[nextInputIndex]}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submitNumber() {
|
||||||
|
const data = this.phoneForm.value;
|
||||||
|
this.isShowNumber = false;
|
||||||
|
if (this.timeLeft) {
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'custom',
|
||||||
|
summary: `Отправить повторно можно через ${this.timeLeft}с`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.jsonrpc.rpc({
|
||||||
|
method: 'sendVerifyByPhone',
|
||||||
|
params: [data.phone]
|
||||||
|
}, RpcService.authService, false).subscribe({
|
||||||
|
next: (result) => {
|
||||||
|
if (result.code !== 0) {
|
||||||
|
this._snackBar.open('Произошла ошибка! Попробуйте позже', '', {
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (result.code === 0) {
|
||||||
|
this.timeLeft = 60;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if(this.timeLeft > 0) {
|
||||||
|
this.timeLeft--;
|
||||||
|
} else {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
},1000)
|
||||||
|
}
|
||||||
|
this.isShowNumber = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error: ', error);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitCode() {
|
||||||
|
const data = this.codeForm.value;
|
||||||
|
this.jsonrpc.rpc({
|
||||||
|
method: 'getTokenByPhone',
|
||||||
|
params: [this.phoneForm.value.phone, Object.values(data).join('')]
|
||||||
|
}, RpcService.authService, false).subscribe({
|
||||||
|
next: (result) => {
|
||||||
|
if (result.code === 0) {
|
||||||
|
this.cookiesService.setCookie('token', result?.data?.token);
|
||||||
|
this.router.navigate(['/'], {
|
||||||
|
queryParams: {
|
||||||
|
token: result?.data?.token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// this.phoneConfirmed.emit(null);
|
||||||
|
} else if (result.code === 230) {
|
||||||
|
this._snackBar.open('Неверный код!', '', {
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
// this.errorConfirmCode = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
backToPhoneForm() {
|
||||||
|
this.codeForm.setValue({
|
||||||
|
code: '',
|
||||||
|
code1: '',
|
||||||
|
code2: '',
|
||||||
|
code3: ''
|
||||||
|
})
|
||||||
|
this.isShowNumber = true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<div class="tab">
|
||||||
|
<input [id]="'tab-' + guid" type="checkbox" name="tabs" />
|
||||||
|
<label [for]="'tab-' + guid">{{ header }}</label>
|
||||||
|
<div class="tab-content">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: solid 1px #bdbdbd;
|
||||||
|
}
|
||||||
|
.tab input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.tab label {
|
||||||
|
display: block;
|
||||||
|
padding: 20px 26px;
|
||||||
|
position: relative;
|
||||||
|
background: #ffffff00;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
// .tab label:before {
|
||||||
|
// display: block;
|
||||||
|
// content: " ";
|
||||||
|
// padding-top: 5px;
|
||||||
|
// }
|
||||||
|
.tab-content {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #292929;
|
||||||
|
-webkit-transition: all 0.35s;
|
||||||
|
-o-transition: all 0.35s;
|
||||||
|
transition: all 0.35s;
|
||||||
|
border-top: solid 1px #bdbdbd;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tab-content p {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
/* :checked */
|
||||||
|
.tab input:checked ~ .tab-content {
|
||||||
|
padding: 16px 26px;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon */
|
||||||
|
.tab label::after {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
line-height: 3;
|
||||||
|
text-align: center;
|
||||||
|
-webkit-transition: all 0.35s;
|
||||||
|
-o-transition: all 0.35s;
|
||||||
|
transition: all 0.35s;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.tab input[type="checkbox"] + label::after {
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
.tab input[type="radio"] + label::after {
|
||||||
|
content: "\25BC";
|
||||||
|
}
|
||||||
|
.tab input[type="checkbox"]:checked + label::after {
|
||||||
|
transform: rotate(315deg);
|
||||||
|
}
|
||||||
|
.tab input[type="radio"]:checked + label::after {
|
||||||
|
transform: rotateX(180deg);
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AccordionComponent } from './accordion.component';
|
||||||
|
|
||||||
|
describe('AccordionComponent', () => {
|
||||||
|
let component: AccordionComponent;
|
||||||
|
let fixture: ComponentFixture<AccordionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AccordionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AccordionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
export interface IAccordionData {
|
||||||
|
header: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-accordion[header]',
|
||||||
|
templateUrl: './accordion.component.html',
|
||||||
|
styleUrls: ['./accordion.component.scss']
|
||||||
|
})
|
||||||
|
export class AccordionComponent implements OnInit {
|
||||||
|
@Input() header!: string
|
||||||
|
public guid: string = uuidv4()
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
<img src="/assets/logo.svg" alt="Логотип">
|
||||||
|
<h3>Горячая линия</h3>
|
||||||
|
<a class="phone-number" href="tel:88003334130">8 (800) 333-41-30</a>
|
||||||
|
<app-social-media-buttons></app-social-media-buttons>
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.24px;
|
||||||
|
color: #6a737c;
|
||||||
|
}
|
||||||
|
.phone-number {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FooterComponent } from './footer.component';
|
||||||
|
|
||||||
|
describe('FooterComponent', () => {
|
||||||
|
let component: FooterComponent;
|
||||||
|
let fixture: ComponentFixture<FooterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ FooterComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(FooterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-footer',
|
||||||
|
templateUrl: './footer.component.html',
|
||||||
|
styleUrls: ['./footer.component.scss']
|
||||||
|
})
|
||||||
|
export class FooterComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
<router-outlet></router-outlet>
|
||||||
|
<app-footer></app-footer>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
* {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { IndexComponent } from './index.component';
|
||||||
|
|
||||||
|
describe('IndexComponent', () => {
|
||||||
|
let component: IndexComponent;
|
||||||
|
let fixture: ComponentFixture<IndexComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ IndexComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(IndexComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-index',
|
||||||
|
templateUrl: './index.component.html',
|
||||||
|
styleUrls: ['./index.component.scss']
|
||||||
|
})
|
||||||
|
export class IndexComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<h2>Пригласи друзей!</h2>
|
||||||
|
<div class="container">
|
||||||
|
<p>
|
||||||
|
Пригласи друзей зарегистрироваться в приложении по твоему уникальному коду и
|
||||||
|
получи бонусы, когда они совершат первую покупку.
|
||||||
|
</p>
|
||||||
|
<button class="share" (click)="share()" [disabled]="loading">
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<mat-icon
|
||||||
|
*ngIf="!loading"
|
||||||
|
aria-hidden="false"
|
||||||
|
aria-label="Share"
|
||||||
|
fontIcon="share"
|
||||||
|
style="color: #fff"
|
||||||
|
></mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #spinner let-diameter>
|
||||||
|
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
:host {
|
||||||
|
padding: 16px;
|
||||||
|
h2 {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share {
|
||||||
|
width: 72px;
|
||||||
|
height: 48px;
|
||||||
|
background: #28af49;
|
||||||
|
padding: 8px 26px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { InviteFriendsComponent } from './invite-friends.component';
|
||||||
|
|
||||||
|
describe('InviteFriendsComponent', () => {
|
||||||
|
let component: InviteFriendsComponent;
|
||||||
|
let fixture: ComponentFixture<InviteFriendsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ InviteFriendsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(InviteFriendsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-invite-friends',
|
||||||
|
templateUrl: './invite-friends.component.html',
|
||||||
|
styleUrls: ['./invite-friends.component.scss']
|
||||||
|
})
|
||||||
|
export class InviteFriendsComponent implements OnInit {
|
||||||
|
public refUrl: string = `${environment.defaultUrl}/?refUserId=`
|
||||||
|
public loading: boolean = true;
|
||||||
|
private shareData: ShareData = {
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private jsonrpc: JsonrpcService,
|
||||||
|
private messageService: MessageService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
const accountData = (await lastValueFrom(
|
||||||
|
this.jsonrpc
|
||||||
|
.rpc(
|
||||||
|
{
|
||||||
|
method: 'getTokenData',
|
||||||
|
params: [],
|
||||||
|
},
|
||||||
|
RpcService.authService,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)).data
|
||||||
|
|
||||||
|
this.refUrl += accountData.user_id
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
if (navigator.share) {
|
||||||
|
navigator.share({
|
||||||
|
title: document.title,
|
||||||
|
text: "Coffee Like",
|
||||||
|
url: this.refUrl
|
||||||
|
})
|
||||||
|
.then(() => console.log('Successful share'))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error sharing:', error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<h2>Ваш предыдущий заказ</h2>
|
||||||
|
<div class="info-order">
|
||||||
|
<p class="flex"><span>Дата: </span>
|
||||||
|
<span *ngIf="!loading">{{(lastOrder?.transactionCreateDate | date:'dd.MM.yyyyг.') || 'Данные не найдены'}}</span>
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</p>
|
||||||
|
<p class="flex"><span>На сумму: </span>
|
||||||
|
<span *ngIf="!loading">{{lastOrder?.orderSum ? lastOrder?.orderSum + ' ₽' : 'Данные не найдены'}}</span>
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="https://yandex.ru/profile/151770398186" target="_blank">
|
||||||
|
<button class="evaluate-order">Оценить заказ</button>
|
||||||
|
</a>
|
||||||
|
<p class="info">
|
||||||
|
Списание бонусов возможно на любые категории. Бонусами можно оплатить 100%
|
||||||
|
суммы покупки. Бонусы начисляются только на напитки с учётом добавок.
|
||||||
|
Неиспользованные бонусы сгорают в течение 90 дней.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ng-template #spinner let-diameter>
|
||||||
|
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
:host {
|
||||||
|
padding: 24px 16px 56px;
|
||||||
|
|
||||||
|
& > h2 {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .info-order {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
span {
|
||||||
|
color: #828282;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.evaluate-order {
|
||||||
|
margin: 24px 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
border: 2px solid #28af49;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #28af49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LastOrderComponent } from './last-order.component';
|
||||||
|
|
||||||
|
describe('LastOrderComponent', () => {
|
||||||
|
let component: LastOrderComponent;
|
||||||
|
let fixture: ComponentFixture<LastOrderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ LastOrderComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LastOrderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Purchase } from 'src/app/interface/data';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-last-order[lastOrder]',
|
||||||
|
templateUrl: './last-order.component.html',
|
||||||
|
styleUrls: ['./last-order.component.scss']
|
||||||
|
})
|
||||||
|
export class LastOrderComponent implements OnInit {
|
||||||
|
@Input() lastOrder!: Purchase;
|
||||||
|
@Input() loading!: boolean;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<div class="container">
|
||||||
|
<mat-icon style="cursor: pointer;" aria-hidden="false" aria-label="Назад" fontIcon="arrow_back_ios" class="back-arrow" (click)="back()"></mat-icon>
|
||||||
|
<h1 class="title">{{title}}</h1>
|
||||||
|
<div class="plug"></div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px 16px;
|
||||||
|
width: 100%;
|
||||||
|
background: #231f20;
|
||||||
|
box-shadow: 0px 8px 16px rgba(17, 17, 17, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.back-arrow {
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.plug {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-family: "Gotham Pro", sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NavbarComponent } from './navbar.component';
|
||||||
|
|
||||||
|
describe('NavbarComponent', () => {
|
||||||
|
let component: NavbarComponent;
|
||||||
|
let fixture: ComponentFixture<NavbarComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ NavbarComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NavbarComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navbar[title]',
|
||||||
|
templateUrl: './navbar.component.html',
|
||||||
|
styleUrls: ['./navbar.component.scss']
|
||||||
|
})
|
||||||
|
export class NavbarComponent implements OnInit {
|
||||||
|
@Input() title: string = 'Название не задано'
|
||||||
|
@Output() backEvent = new EventEmitter<null>();
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
back() {
|
||||||
|
this.backEvent.emit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<a *ngFor="let link of links" [href]="link.url" target="_blank">
|
||||||
|
<img [src]="link.imgUrl" [alt]="link.label" />
|
||||||
|
</a>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: space-between;
|
||||||
|
a {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: #333333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SocialMediaButtonsComponent } from './social-media-buttons.component';
|
||||||
|
|
||||||
|
describe('SocialMediaButtonsComponent', () => {
|
||||||
|
let component: SocialMediaButtonsComponent;
|
||||||
|
let fixture: ComponentFixture<SocialMediaButtonsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SocialMediaButtonsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SocialMediaButtonsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
export interface ISocialMediaLink {
|
||||||
|
url: string;
|
||||||
|
imgUrl: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-social-media-buttons',
|
||||||
|
templateUrl: './social-media-buttons.component.html',
|
||||||
|
styleUrls: ['./social-media-buttons.component.scss']
|
||||||
|
})
|
||||||
|
export class SocialMediaButtonsComponent implements OnInit {
|
||||||
|
public links: ISocialMediaLink[] = [
|
||||||
|
{
|
||||||
|
label: 'Инстаграм',
|
||||||
|
url: 'https://www.instagram.com/',
|
||||||
|
imgUrl: '/assets/social-media-icons/instagram.svg'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ВК',
|
||||||
|
url: 'https://vk.com/coffeelike_com',
|
||||||
|
imgUrl: '/assets/social-media-icons/vk.svg'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Youtube',
|
||||||
|
url: 'https://www.youtube.com/c/coffeelikeru',
|
||||||
|
imgUrl: '/assets/social-media-icons/youtube.svg'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
|
||||||
|
import { AuthGuard } from 'src/app/guards/auth-guard.guard';
|
||||||
|
import { LoginComponent } from './pages/login/login.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: GuestCardComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
component: LoginComponent
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class FirstOptionRoutingModule {}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FirstOptionRoutingModule } from './first-option-routing.module';
|
||||||
|
import { IndexComponent } from './components/index/index.component';
|
||||||
|
import { FooterComponent } from './components/footer/footer.component';
|
||||||
|
import { SocialMediaButtonsComponent } from './components/social-media-buttons/social-media-buttons.component';
|
||||||
|
import { NavbarComponent } from './components/navbar/navbar.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
|
||||||
|
import { QrCodeModule } from 'ng-qrcode';
|
||||||
|
import { AccordionComponent } from './components/accordion/accordion.component';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { LastOrderComponent } from './components/last-order/last-order.component';
|
||||||
|
import { InviteFriendsComponent } from './components/invite-friends/invite-friends.component';
|
||||||
|
import { LoginComponent } from './pages/login/login.component';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { NgxMatIntlTelInputComponent } from 'ngx-mat-intl-tel-input';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { DirectivesModule } from 'src/app/directives/directives.module';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
IndexComponent,
|
||||||
|
FooterComponent,
|
||||||
|
SocialMediaButtonsComponent,
|
||||||
|
NavbarComponent,
|
||||||
|
GuestCardComponent,
|
||||||
|
AccordionComponent,
|
||||||
|
LastOrderComponent,
|
||||||
|
InviteFriendsComponent,
|
||||||
|
LoginComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FirstOptionRoutingModule,
|
||||||
|
MatIconModule,
|
||||||
|
QrCodeModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
NgxMatIntlTelInputComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
DirectivesModule
|
||||||
|
],
|
||||||
|
bootstrap: [IndexComponent],
|
||||||
|
})
|
||||||
|
export class FirstOptionModule { }
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
<app-navbar title="Карта гостя" (backEvent)="logout()"></app-navbar>
|
||||||
|
|
||||||
|
<div class="guest-card">
|
||||||
|
<div class="guest-card__qr" (click)="qrCodeClick()">
|
||||||
|
<ng-container *ngIf="customerInfo">
|
||||||
|
<qr-code
|
||||||
|
[value]="customerInfo?.phone.substr(2) || 'Данные не найдены'"
|
||||||
|
[margin]="0"
|
||||||
|
[size]="qrCodeSize"
|
||||||
|
errorCorrectionLevel="M"
|
||||||
|
></qr-code>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!customerInfo">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 85 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="guest-card__user-description">
|
||||||
|
Текущий баланс бонусов:
|
||||||
|
<span *ngIf="customerInfo">
|
||||||
|
{{ getBalanceAmount(customerInfo?.walletBalances) }}
|
||||||
|
бонусов</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<span id="bonuses-condition"></span>
|
||||||
|
<app-accordion header="Условия начисления бонусов">
|
||||||
|
<p>
|
||||||
|
Расчет начисления бонусов - 10% от суммы покупок за период с 11.01.2023г.
|
||||||
|
по 31.03.2023 г.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
За период с 11.01.2023г. по 31.03.2023 г. сумма ваших покупок составила
|
||||||
|
3700 руб.
|
||||||
|
</p>
|
||||||
|
<p>Начисляемый бонус 10% от суммы покупок</p>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
Возврат покупки, за которую бонусы были начислены:
|
||||||
|
<li>
|
||||||
|
В случае, если бонусов на счету достаточно для списания, бонусы
|
||||||
|
списываются в полном ранее начисленном за возвращаемый товар объеме.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
В случае, если бонусов на счету недостаточно, формируется минусовой
|
||||||
|
баланс.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
Возврат покупки, которая была оплачена бонусами:
|
||||||
|
<li>
|
||||||
|
В случае предъявления Участником кассового или товарного чека, сумма
|
||||||
|
бонусов, списанная для оплаты возвращаемого товара, зачисляется на счет
|
||||||
|
участника.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
В случае возврата товара с применением оплаты бонусами, клиенту
|
||||||
|
возвращается денежная сумма в размере, внесенном Участником в оплату
|
||||||
|
товара при покупке, за вычетом суммы, оплаченной бонусами.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</app-accordion>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="guest-card__purchases-description"
|
||||||
|
[ngStyle]="{
|
||||||
|
display: purchaseData.$loading ? 'grid' : 'block'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Сумма ваших покупок за период с
|
||||||
|
{{ purchaseData.currentPeriod[0].format("DD.MM.yyyyг.") }} -
|
||||||
|
<span *ngIf="!purchaseData.$loading"
|
||||||
|
>{{ purchaseData.currentAmount }} руб.</span
|
||||||
|
>
|
||||||
|
<ng-container *ngIf="purchaseData.$loading">
|
||||||
|
<ng-container
|
||||||
|
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
|
||||||
|
></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<app-accordion header="Узнать % начисляемых бонусов">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="guest-card__level-info">
|
||||||
|
<ng-container *ngIf="!purchaseData.$loading">
|
||||||
|
<ng-container *ngIf="currentLvlPeriod.end">
|
||||||
|
<h2>
|
||||||
|
До следующего уровня за период с
|
||||||
|
{{ purchaseData.currentPeriod[0].format("DD.MM.yyyyг") }} по
|
||||||
|
{{ purchaseData.currentPeriod[1].format("DD.MM.yyyyг") }}
|
||||||
|
осталось совершить покупки на {{ currentLvlPeriod.end - (purchaseData.currentAmount || 0) + 1 }} рублей
|
||||||
|
</h2>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<p class="show-more">
|
||||||
|
<a href="#bonuses-condition">
|
||||||
|
Узнать условия начисления бонусов
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<app-last-order
|
||||||
|
[lastOrder]="lastPurchase"
|
||||||
|
[loading]="purchaseData.$loading"
|
||||||
|
></app-last-order>
|
||||||
|
<hr />
|
||||||
|
<app-invite-friends></app-invite-friends>
|
||||||
|
<hr />
|
||||||
|
<div class="guest-card__download-app">
|
||||||
|
<img src="/assets/download-app.svg" alt="Скачай приложение" />
|
||||||
|
</div>
|
||||||
|
<a class="guest-card__loyalty-program" routerLink="loyalty-program"
|
||||||
|
>Подробнее о правилах <br />
|
||||||
|
Программы лояльности</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #spinner let-diameter>
|
||||||
|
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,200 @@
|
|||||||
|
:host {
|
||||||
|
.guest-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0 0;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
color: red !important;
|
||||||
|
|
||||||
|
&__qr {
|
||||||
|
padding: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
#fff 33%,
|
||||||
|
transparent 0px,
|
||||||
|
transparent 67%,
|
||||||
|
#fff 0px
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
#ffe 33%,
|
||||||
|
transparent 0px,
|
||||||
|
transparent 66%,
|
||||||
|
#fff 0px
|
||||||
|
),
|
||||||
|
linear-gradient(#fff 33%, transparent 0px, transparent 67%, #fff 0),
|
||||||
|
linear-gradient(90deg, #fff 33%, transparent 0, transparent 66%, #fff 0);
|
||||||
|
background-size: 1px 100%, 100% 1px, 1px 100%, 100% 1px;
|
||||||
|
background-position: 0 0, 0 0, 100% 100%, 100% 100%;
|
||||||
|
background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__user-description {
|
||||||
|
margin: 18px 0 0;
|
||||||
|
padding: 14px 24px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-top: 1px solid #fff;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
// font-family: "Goldman";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 19px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #f2994a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__purchases-description {
|
||||||
|
margin: 0;
|
||||||
|
padding: 14px 24px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
// font-family: "Goldman";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 19px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
grid-template-columns: calc(100% - 24px) 24px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #219653;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__level-info {
|
||||||
|
padding: 36px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
background: #231f20;
|
||||||
|
box-shadow: 0px 0px 3px #f2c94c59;
|
||||||
|
background-image: linear-gradient(#f2c94c, #f2c94c);
|
||||||
|
background-size: 70% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #f2c94c;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background-color: #f2c94c;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: #f2c94c;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f2c94c;
|
||||||
|
cursor: ew-resize;
|
||||||
|
box-shadow: 0 0 2px 0 #555;
|
||||||
|
transition: background 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f2c94c;
|
||||||
|
cursor: ew-resize;
|
||||||
|
box-shadow: 0 0 2px 0 #555;
|
||||||
|
transition: background 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .show-more {
|
||||||
|
margin-top: 42px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #28af49;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__download-app {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 32px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: calc(100% - 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loyalty-program {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 17px 0 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app-accordion {
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
list-style-type: disc;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GuestCardComponent } from './guest-card.component';
|
||||||
|
|
||||||
|
describe('GuestCardComponent', () => {
|
||||||
|
let component: GuestCardComponent;
|
||||||
|
let fixture: ComponentFixture<GuestCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ GuestCardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GuestCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ExitComponent } from 'src/app/components/exit/exit.component';
|
||||||
|
import { CookiesService } from 'src/app/services/cookies.service';
|
||||||
|
import { WpJsonService } from 'src/app/services/wp-json.service';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Purchase, lvlPeriod } from 'src/app/interface/data';
|
||||||
|
import { lvlPeriods } from 'src/app/app.constants';
|
||||||
|
|
||||||
|
interface Moment extends moment.Moment {}
|
||||||
|
|
||||||
|
export interface IPurchaseData {
|
||||||
|
currentPeriod: Moment[];
|
||||||
|
lastPeriod: Moment[];
|
||||||
|
lastPurchases: Purchase[];
|
||||||
|
currentPurchases: Purchase[];
|
||||||
|
lastAmount?: number;
|
||||||
|
currentAmount?: number;
|
||||||
|
$loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-guest-card',
|
||||||
|
templateUrl: './guest-card.component.html',
|
||||||
|
styleUrls: ['./guest-card.component.scss'],
|
||||||
|
})
|
||||||
|
export class GuestCardComponent implements OnInit {
|
||||||
|
public qrCodeSize: number = 85;
|
||||||
|
private isQrCodeClicked: boolean = false;
|
||||||
|
public customerInfo!: any;
|
||||||
|
public purchases!: Purchase[];
|
||||||
|
public lastPurchase!: Purchase;
|
||||||
|
public purchaseData: IPurchaseData = {
|
||||||
|
currentPeriod: [],
|
||||||
|
lastPeriod: [],
|
||||||
|
lastPurchases: [],
|
||||||
|
currentPurchases: [],
|
||||||
|
$loading: true,
|
||||||
|
get currentAmount():number {
|
||||||
|
const amount = this.currentPurchases.reduce((accumulator, currentValue) => {
|
||||||
|
if (currentValue.transactionType !== 'PayFromWallet') return accumulator;
|
||||||
|
return accumulator + currentValue.orderSum!;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return amount * 1
|
||||||
|
},
|
||||||
|
get lastAmount():number {
|
||||||
|
const amount = this.lastPurchases.reduce((accumulator, currentValue) => {
|
||||||
|
if (currentValue.transactionType !== 'PayFromWallet') return accumulator;
|
||||||
|
return accumulator + currentValue.orderSum!;
|
||||||
|
}, 0);
|
||||||
|
return amount * 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public discountLevel: number = 4.2;
|
||||||
|
public lvlPeriods: lvlPeriod[] = lvlPeriods;
|
||||||
|
public currentLvlPeriod!: lvlPeriod;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _bottomSheet: MatBottomSheet,
|
||||||
|
private cookiesService: CookiesService,
|
||||||
|
private router: Router,
|
||||||
|
private wpJsonService: WpJsonService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const token = this.cookiesService.getItem('token');
|
||||||
|
this.getCurrentQuarterOfYear();
|
||||||
|
this.wpJsonService
|
||||||
|
.getCustomerInfo(
|
||||||
|
environment.systemId,
|
||||||
|
token || '',
|
||||||
|
environment.icardProxy
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (value) => {
|
||||||
|
this.customerInfo = value.customer_info;
|
||||||
|
this.getPurchases().subscribe((value) => {
|
||||||
|
this.purchases = value[this.customerInfo?.id].filter(
|
||||||
|
(purchase: Purchase) =>
|
||||||
|
[
|
||||||
|
'PayFromWallet',
|
||||||
|
'CancelPayFromWallet',
|
||||||
|
'RefillWallet',
|
||||||
|
'RefillWalletFromOrder'
|
||||||
|
].includes(purchase.transactionType || '')
|
||||||
|
);
|
||||||
|
this.lastPurchase = this.purchases.filter(
|
||||||
|
(purchase: Purchase) =>
|
||||||
|
[
|
||||||
|
'PayFromWallet',
|
||||||
|
'RefillWalletFromOrder'
|
||||||
|
].includes(purchase.transactionType || '')
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
this.purchaseData.lastPurchases = this.purchases.filter((value: Purchase) => {
|
||||||
|
return moment(value.transactionCreateDate).isBetween(this.purchaseData.lastPeriod[0], this.purchaseData.lastPeriod[1])
|
||||||
|
})
|
||||||
|
this.purchaseData.currentPurchases = this.purchases.filter((value: Purchase) => {
|
||||||
|
return moment(value.transactionCreateDate).isBetween(this.purchaseData.currentPeriod[0], this.purchaseData.currentPeriod[1])
|
||||||
|
})
|
||||||
|
const currentAmount = this.purchaseData.currentAmount || 0
|
||||||
|
this.currentLvlPeriod = this.lvlPeriods.find((item) => item.start <= currentAmount && currentAmount <= (item.end || Infinity))!
|
||||||
|
this.purchaseData.$loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
qrCodeClick() {
|
||||||
|
this.isQrCodeClicked = !this.isQrCodeClicked;
|
||||||
|
this.qrCodeSize = this.isQrCodeClicked ? 180 : 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteToken(): void {
|
||||||
|
this.cookiesService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
const bottomSheet = this._bottomSheet.open(ExitComponent);
|
||||||
|
bottomSheet.afterDismissed().subscribe({
|
||||||
|
next: (val) => {
|
||||||
|
if (val) {
|
||||||
|
this.deleteToken();
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPurchases(
|
||||||
|
start: Date | Moment = moment().subtract(1, 'months').startOf('month'),
|
||||||
|
end: Date | Moment = moment()
|
||||||
|
): Observable<any> {
|
||||||
|
const token = this.cookiesService.getItem('token');
|
||||||
|
const delta = moment(end).diff(moment(start), 'days');
|
||||||
|
return this.wpJsonService.getTransactions(
|
||||||
|
environment.systemId,
|
||||||
|
token ?? '',
|
||||||
|
environment.icardProxy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentQuarterOfYear() {
|
||||||
|
const quarters = [
|
||||||
|
[
|
||||||
|
moment().subtract(1, 'years').endOf('year').subtract(3, 'months'),
|
||||||
|
moment().subtract(1, 'years').endOf('year'),
|
||||||
|
],
|
||||||
|
[moment().startOf('year'), moment().startOf('year').add(3, 'months')],
|
||||||
|
[
|
||||||
|
moment().startOf('year').add(3, 'months'),
|
||||||
|
moment().startOf('year').add(6, 'months'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
moment().startOf('year').add(6, 'months'),
|
||||||
|
moment().startOf('year').add(9, 'months'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
moment().startOf('year').add(9, 'months'),
|
||||||
|
moment().startOf('year').add(12, 'months'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
if (moment().isBetween(quarters[i][0], quarters[i][1])) {
|
||||||
|
this.purchaseData.lastPeriod = quarters[i - 1];
|
||||||
|
this.purchaseData.currentPeriod = quarters[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBalanceAmount(loyaltyPrograms: any[]) {
|
||||||
|
return loyaltyPrograms.reduce((accumulator, currentValue) => {
|
||||||
|
return accumulator + currentValue.balance;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
<app-navbar
|
||||||
|
*ngIf="phoneForm.value.phone && !isShowNumber"
|
||||||
|
[title]="phoneForm.value.phone"
|
||||||
|
(backEvent)="backToPhoneForm()"
|
||||||
|
[ngStyle]="{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0
|
||||||
|
}"
|
||||||
|
></app-navbar>
|
||||||
|
<h1>Участвуй в программе лояльности COFFEE LIKE</h1>
|
||||||
|
<p class="description">Начни получать бонусы прямо сейчас</p>
|
||||||
|
<form
|
||||||
|
*ngIf="isShowNumber; else smsCode"
|
||||||
|
(ngSubmit)="submitNumber()"
|
||||||
|
[formGroup]="phoneForm"
|
||||||
|
>
|
||||||
|
<!-- <div class="input-container"> -->
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Ваше имя</mat-label>
|
||||||
|
<input formControlName="name" matInput type="text" />
|
||||||
|
</mat-form-field>
|
||||||
|
<!-- </div> -->
|
||||||
|
<div class="input-container">
|
||||||
|
<!-- <label for="number">Номер телефона</label>
|
||||||
|
<input id="number" type="text" placeholder="Введите номер" /> -->
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Номер телефона</mat-label>
|
||||||
|
<ngx-mat-intl-tel-input
|
||||||
|
formControlName="phone"
|
||||||
|
[enablePlaceholder]="true"
|
||||||
|
[enableSearch]="false"
|
||||||
|
[onlyCountries]="['ru', 'kg', 'by', 'kz', 'fi', 'de']"
|
||||||
|
[preferredCountries]="['ru']"
|
||||||
|
name="phone"
|
||||||
|
#phone
|
||||||
|
>
|
||||||
|
</ngx-mat-intl-tel-input>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<p class="offer">
|
||||||
|
Используя приложение, вы принимаете условия в <span>соглашениях</span> и
|
||||||
|
соглашаетесь на получение рекламно-информационных сообщений
|
||||||
|
</p>
|
||||||
|
<button [disabled]="phoneForm.invalid">Принять участие</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ng-template #smsCode>
|
||||||
|
<h2>Введите код из SMS</h2>
|
||||||
|
<form class="code-form" [formGroup]="codeForm" (ngSubmit)="submitCode()">
|
||||||
|
<div class="inputs-container">
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field1"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field1
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code1"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field2"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field2
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code2"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
<label class="box"
|
||||||
|
><input
|
||||||
|
class="field"
|
||||||
|
id="field3"
|
||||||
|
type="tel"
|
||||||
|
placeholder="•"
|
||||||
|
#field3
|
||||||
|
[appFocusNextInput]="inputFocusEmitter"
|
||||||
|
formControlName="code3"
|
||||||
|
maxlength="1"
|
||||||
|
/></label>
|
||||||
|
</div>
|
||||||
|
<button>Войти</button>
|
||||||
|
</form>
|
||||||
|
<p class="resend-code">
|
||||||
|
Не пришло SMS?<br />
|
||||||
|
<ng-container *ngIf="timeLeft">
|
||||||
|
Отправим повторно через {{timeLeft}}с
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!timeLeft">
|
||||||
|
<span class="resend" (click)="submitNumber()">Отправить повторно</span>
|
||||||
|
</ng-container>
|
||||||
|
</p>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,215 @@
|
|||||||
|
:host {
|
||||||
|
padding-top: 48px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 52px;
|
||||||
|
color: red !important;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 302px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 44px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
width: 180px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 35px;
|
||||||
|
|
||||||
|
.offer {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-family: "Gowun Dodum";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 17px;
|
||||||
|
text-align: center;
|
||||||
|
span {
|
||||||
|
color: #13a538;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6a737c;
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px 16px 8px;
|
||||||
|
background-color: #252323;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: none;
|
||||||
|
border-top: solid #6a737c 1px;
|
||||||
|
border-bottom: solid #6a737c 1px;
|
||||||
|
color: #6a737c;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 46px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
padding: 10px 24px;
|
||||||
|
background: #28af49;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
letter-spacing: -0.408px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
&:disabled {
|
||||||
|
background-color: #344a3a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-form {
|
||||||
|
width: 100%;
|
||||||
|
// input {
|
||||||
|
// width: 100%;
|
||||||
|
// padding: 24px 16px 8px;
|
||||||
|
// background-color: #252323;
|
||||||
|
// margin-bottom: 16px;
|
||||||
|
// border: none;
|
||||||
|
// border-top: solid #6a737c 1px;
|
||||||
|
// border-bottom: solid #6a737c 1px;
|
||||||
|
// color: #6a737c;
|
||||||
|
// font-style: normal;
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-size: 22px;
|
||||||
|
// line-height: 28px;
|
||||||
|
// }
|
||||||
|
.inputs-container {
|
||||||
|
width: 102px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
// code
|
||||||
|
.box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
border-radius: 6px;
|
||||||
|
// box-shadow: 0 0 6px 1px hsla(240, 54%, 61%, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.box:focus-within {
|
||||||
|
box-shadow: 0 0 6px 1px rgba($color: #28af49, $alpha: 0.2),
|
||||||
|
0 0 0 2px rgba($color: #28af49, $alpha: 0.6);
|
||||||
|
}
|
||||||
|
.box::before,
|
||||||
|
.box::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.box::before {
|
||||||
|
// background: hsl(240, 54%, 97%);
|
||||||
|
z-index: 1;
|
||||||
|
transition: background-color 450ms cubic-bezier(0.25, 0.01, 0.25, 1);
|
||||||
|
}
|
||||||
|
.box::after {
|
||||||
|
transform: translateY(100%);
|
||||||
|
background-color: hsl(145, 0%, 42%);
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 10;
|
||||||
|
transition: transform 450ms cubic-bezier(0.25, 0.01, 0.25, 1),
|
||||||
|
opacity 450ms cubic-bezier(0.25, 0.01, 0.25, 1),
|
||||||
|
background-color 450ms cubic-bezier(0.25, 0.01, 0.25, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
position: relative;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 25.21px;
|
||||||
|
line-height: 42px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.field::placeholder {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resend-code {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 23px;
|
||||||
|
.resend {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
describe('LoginComponent', () => {
|
||||||
|
let component: LoginComponent;
|
||||||
|
let fixture: ComponentFixture<LoginComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ LoginComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LoginComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
OnInit
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { CookiesService } from 'src/app/services/cookies.service';
|
||||||
|
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.scss'],
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit, AfterViewInit {
|
||||||
|
public isShowNumber: boolean = true;
|
||||||
|
public phoneForm = new FormGroup({
|
||||||
|
name: new FormControl('', []),
|
||||||
|
phone: new FormControl('', [Validators.required]),
|
||||||
|
});
|
||||||
|
public codeForm = new FormGroup({
|
||||||
|
code: new FormControl('', [Validators.required]),
|
||||||
|
code1: new FormControl('', [Validators.required]),
|
||||||
|
code2: new FormControl('', [Validators.required]),
|
||||||
|
code3: new FormControl('', [Validators.required]),
|
||||||
|
});
|
||||||
|
private inputIds = ['field', 'field1', 'field2', 'field3'];
|
||||||
|
timeLeft: number = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cookiesService: CookiesService,
|
||||||
|
private router: Router,
|
||||||
|
private jsonrpc: JsonrpcService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private _snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
public inputFocusEmitter = new EventEmitter<string>();
|
||||||
|
|
||||||
|
@HostListener('window:keyup', ['$event'])
|
||||||
|
HandlKeyEvents(event: any) {
|
||||||
|
if (!event.target.classList.contains('field')) return;
|
||||||
|
const key = event.key.toLocaleLowerCase();
|
||||||
|
let elementId = '';
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'backspace':
|
||||||
|
elementId = event.target.id;
|
||||||
|
event.target.value = '';
|
||||||
|
const prevInputIndex = this.inputIds.indexOf(elementId) - 1;
|
||||||
|
if (prevInputIndex >= 0) {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[prevInputIndex]}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
elementId = event.target.id;
|
||||||
|
const index = this.inputIds.indexOf(elementId);
|
||||||
|
const nextInputIndex = index + 1;
|
||||||
|
if (event.target.value.length > 1) {
|
||||||
|
event.target.value = event.target.value.slice(-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (nextInputIndex > 0 && nextInputIndex <= this.inputIds.length) {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[nextInputIndex]}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submitNumber() {
|
||||||
|
const data = this.phoneForm.value;
|
||||||
|
this.isShowNumber = false;
|
||||||
|
if (this.timeLeft) {
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'custom',
|
||||||
|
summary: `Отправить повторно можно через ${this.timeLeft}с`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.jsonrpc.rpc({
|
||||||
|
method: 'sendVerifyByPhone',
|
||||||
|
params: [data.phone]
|
||||||
|
}, RpcService.authService, false).subscribe({
|
||||||
|
next: (result) => {
|
||||||
|
if (result.code !== 0) {
|
||||||
|
this._snackBar.open('Произошла ошибка! Попробуйте позже', '', {
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (result.code === 0) {
|
||||||
|
this.timeLeft = 60;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if(this.timeLeft > 0) {
|
||||||
|
this.timeLeft--;
|
||||||
|
} else {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
},1000)
|
||||||
|
}
|
||||||
|
this.isShowNumber = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error: ', error);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitCode() {
|
||||||
|
const data = this.codeForm.value;
|
||||||
|
this.jsonrpc.rpc({
|
||||||
|
method: 'getTokenByPhone',
|
||||||
|
params: [this.phoneForm.value.phone, Object.values(data).join('')]
|
||||||
|
}, RpcService.authService, false).subscribe({
|
||||||
|
next: (result) => {
|
||||||
|
if (result.code === 0) {
|
||||||
|
this.cookiesService.setCookie('token', result?.data?.token);
|
||||||
|
this.router.navigate(['/'], {
|
||||||
|
queryParams: {
|
||||||
|
token: result?.data?.token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// this.phoneConfirmed.emit(null);
|
||||||
|
} else if (result.code === 230) {
|
||||||
|
this._snackBar.open('Неверный код!', '', {
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
// this.errorConfirmCode = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
backToPhoneForm() {
|
||||||
|
this.codeForm.setValue({
|
||||||
|
code: '',
|
||||||
|
code1: '',
|
||||||
|
code2: '',
|
||||||
|
code3: ''
|
||||||
|
})
|
||||||
|
this.isShowNumber = true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<p>test works!</p>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TestComponent } from './test.component';
|
||||||
|
|
||||||
|
describe('TestComponent', () => {
|
||||||
|
let component: TestComponent;
|
||||||
|
let fixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ TestComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(TestComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-test',
|
||||||
|
templateUrl: './test.component.html',
|
||||||
|
styleUrls: ['./test.component.scss']
|
||||||
|
})
|
||||||
|
export class TestComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { TestComponent } from './components/test/test.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: TestComponent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class SecondOptionRoutingModule {}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TestComponent } from './components/test/test.component';
|
||||||
|
import { SecondOptionRoutingModule } from './second-option-routing.module';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SecondOptionRoutingModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SecondOptionModule { }
|
||||||
@ -42,4 +42,10 @@ export class CookiesService {
|
|||||||
deleteCookie(key: string): void {
|
deleteCookie(key: string): void {
|
||||||
this.setCookie(key, '', { 'max-age': -1 });
|
this.setCookie(key, '', { 'max-age': -1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.deleteCookie('token')
|
||||||
|
this.deleteCookie('phone-number')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
128
angular/src/app/services/loyalty-program.service.ts
Normal file
128
angular/src/app/services/loyalty-program.service.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Moment, Purchase } from '../interface/data';
|
||||||
|
|
||||||
|
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: true,
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user