Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/features/home/details/actions/actions.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<div class="page-content">
<ng-container *ngFor="let action of actions$ | ngrxPush">
<ion-card (click)="openAction(action)">
<ion-card (click)="doAction(action)">
<ion-card-header>
<div class="wrapper">
<div>
Expand Down
132 changes: 96 additions & 36 deletions src/app/features/home/details/actions/actions.page.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { TranslocoService } from '@ngneat/transloco';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { combineLatest } from 'rxjs';
import { combineLatest, of } from 'rxjs';
import { catchError, concatMap, first, map, tap } from 'rxjs/operators';
import { ActionsDialogComponent } from '../../../../shared/actions/actions-dialog/actions-dialog.component';
import {
Expand All @@ -13,7 +14,12 @@ import {
} from '../../../../shared/actions/service/actions.service';
import { BlockingActionService } from '../../../../shared/blocking-action/blocking-action.service';
import { DiaBackendAuthService } from '../../../../shared/dia-backend/auth/dia-backend-auth.service';
import {
DiaBackendStoreService,
NetworkAppOrderStatus,
} from '../../../../shared/dia-backend/store/dia-backend-store.service';
import { ErrorService } from '../../../../shared/error/error.service';
import { OrderDetailDialogComponent } from '../../../../shared/order-detail-dialog/order-detail-dialog.component';
import { isNonNullable } from '../../../../utils/rx-operators/rx-operators';

@UntilDestroy()
Expand All @@ -39,54 +45,108 @@ export class ActionsPage {
private readonly route: ActivatedRoute,
private readonly authService: DiaBackendAuthService,
private readonly snackBar: MatSnackBar,
private readonly dialog: MatDialog
private readonly dialog: MatDialog,
private readonly storeService: DiaBackendStoreService
) {}

openAction(action: Action) {
openActionDialog$(action: Action) {
return combineLatest([
this.actionsService.getParams$(action.params_list_custom_param1),
this.authService.token$,
this.id$,
])
]).pipe(
first(),
concatMap(([params, token, id]) => {
const dialogRef = this.dialog.open<ActionsDialogComponent>(
ActionsDialogComponent,
{
disableClose: true,
data: {
action: action,
params: params,
},
}
);
return dialogRef.afterClosed().pipe(
isNonNullable(),
concatMap(data =>
of({
networkApp: action.network_app_id_text,
actionArgs: { ...data, token: token, cid: id },
} as CreateOrderInput)
)
);
})
);
}

openOrderDialog$(orderStatus: NetworkAppOrderStatus) {
const dialogRef = this.dialog.open<OrderDetailDialogComponent>(
OrderDetailDialogComponent,
{
disableClose: true,
data: orderStatus,
width: '80%',
}
);
return dialogRef.afterClosed().pipe(
isNonNullable(),
concatMap((orderId: string) => of(orderId))
);
}

createOrder$(appName: string, actionArgs: any) {
return this.storeService.createNetworkAppOrder(appName, actionArgs).pipe(
catchError((err: unknown) => {
return this.errorService.toastError$(err);
}),
isNonNullable()
);
}

confirmOrder$(id: string) {
return this.storeService.confirmNetworkAppOrder(id).pipe(
catchError((err: unknown) => {
if (err instanceof HttpErrorResponse) {
const errorType = err.error.error?.type;
if (errorType === 'insufficient_fund')
return this.errorService.toastError$(
this.translocoService.translate(`error.diaBackend.${errorType}`)
);
}
return this.errorService.toastError$(err);
}),
isNonNullable()
);
}

doAction(action: Action) {
this.openActionDialog$(action)
.pipe(
first(),
concatMap(([params, token, id]) => {
const dialogRef = this.dialog.open<ActionsDialogComponent>(
ActionsDialogComponent,
{
disableClose: true,
data: {
action: action,
params: params,
},
}
);
return dialogRef.afterClosed().pipe(
isNonNullable(),
tap(data =>
this.sendAction(action, { ...data, token: token, cid: id })
concatMap(createOrderInput =>
this.blockingActionService.run$(
this.createOrder$(
createOrderInput.networkApp,
createOrderInput.actionArgs
)
)
),
concatMap(orderStatus => this.openOrderDialog$(orderStatus)),
concatMap(orderId =>
this.blockingActionService.run$(this.confirmOrder$(orderId))
),
tap(() => {
this.snackBar.open(
this.translocoService.translate('message.sentSuccessfully')
);
}),
untilDestroyed(this)
)
.subscribe();
}
}

sendAction(action: Action, body: any) {
const action$ = this.actionsService.send$(action.base_url_text, body).pipe(
catchError((err: unknown) => {
return this.errorService.toastError$(err);
}),
tap(() =>
this.snackBar.open(
this.translocoService.translate('message.sentSuccessfully')
)
)
);
this.blockingActionService
.run$(action$)
.pipe(untilDestroyed(this))
.subscribe();
}
interface CreateOrderInput {
networkApp: string;
actionArgs: any;
}
1 change: 1 addition & 0 deletions src/app/shared/actions/service/actions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface Action {
readonly description_text: string;
readonly params_list_custom_param1: string[];
readonly title_text: string;
readonly network_app_id_text: string;
}

export interface Param {
Expand Down
18 changes: 18 additions & 0 deletions src/app/shared/dia-backend/store/dia-backend-store.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TestBed } from '@angular/core/testing';
import { SharedTestingModule } from '../../shared-testing.module';
import { DiaBackendStoreService } from './dia-backend-store.service';

describe('DiaBackendStoreService', () => {
let service: DiaBackendStoreService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [SharedTestingModule],
});
service = TestBed.inject(DiaBackendStoreService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
63 changes: 63 additions & 0 deletions src/app/shared/dia-backend/store/dia-backend-store.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { defer } from 'rxjs';
import { concatMap } from 'rxjs/operators';
import { DiaBackendAuthService } from '../auth/dia-backend-auth.service';
import { BASE_URL } from '../secret';

@Injectable({
providedIn: 'root',
})
export class DiaBackendStoreService {
constructor(
private readonly httpClient: HttpClient,
private readonly authService: DiaBackendAuthService
) {}

createNetworkAppOrder(networkApp: string, actionArgs: any) {
return defer(() => this.authService.getAuthHeadersWithApiKey()).pipe(
concatMap(headers => {
return this.httpClient.post<NetworkAppOrderStatus>(
`${BASE_URL}/api/v3/store/network-app-orders/`,
{
network_app: networkApp,
action_args: actionArgs,
},
{ headers }
);
})
);
}

confirmNetworkAppOrder(id: string) {
return defer(() => this.authService.getAuthHeadersWithApiKey()).pipe(
concatMap(headers => {
return this.httpClient.post<NetworkAppOrderStatus>(
`${BASE_URL}/api/v3/store/network-app-orders/${id}/confirm/`,
{},
{ headers }
);
})
);
}
}

export interface NetworkAppOrderStatus {
id: string;
status: 'completed' | 'canceled' | 'pending';
network_app: string;
action: string;
action_args: Record<string, unknown>;
price: string;
fee: string | null;
total_cost: string;
quantity: number;
fund_crypto_transaction_id: string;
fund_tx_hash: string;
payment_crypto_transaction_id: string;
payment_tx_hash: string;
workflow_id: string;
owner: string;
created_at: string;
expired_at: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div *transloco="let t">
<h2 mat-dialog-title>{{ t('payment.confirmPayment') }}</h2>
<ion-row style="border-bottom: groove">
<ion-col>
<ion-label>{{ t('payment.price') }}</ion-label>
</ion-col>
<ion-col align="end">
<ion-label>{{ orderStatus.price | number: '1.2-2' }} NUM</ion-label>
</ion-col>
</ion-row>
<ion-row style="border-bottom: groove">
<ion-col>
<ion-label>{{ t('payment.fee') }}</ion-label>
</ion-col>
<ion-col align="end">
<ion-label>{{ orderStatus.fee | number: '1.2-2' }} NUM</ion-label>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-label>{{ t('payment.totalCost') }}</ion-label>
</ion-col>
<ion-col align="end">
<ion-label>{{ orderStatus.total_cost | number: '1.2-2' }} NUM</ion-label>
</ion-col>
</ion-row>
<mat-dialog-actions align="end">
<button mat-button (click)="cancel()">{{ t('cancel') }}</button>
<button mat-button color="primary" (click)="ok()">
{{ t('ok') }}
</button>
</mat-dialog-actions>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { SharedTestingModule } from '../../shared/shared-testing.module';
import { OrderDetailDialogComponent } from './order-detail-dialog.component';

describe('ActionsDialogComponent', () => {
let component: OrderDetailDialogComponent;
let fixture: ComponentFixture<OrderDetailDialogComponent>;

beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [OrderDetailDialogComponent],
imports: [SharedTestingModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: {} },
],
}).compileComponents();

fixture = TestBed.createComponent(OrderDetailDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
})
);

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { NetworkAppOrderStatus } from '../dia-backend/store/dia-backend-store.service';

@Component({
selector: 'app-order-detail-dialog',
templateUrl: './order-detail-dialog.component.html',
styleUrls: ['./order-detail-dialog.component.scss'],
})
export class OrderDetailDialogComponent {
readonly orderStatus: NetworkAppOrderStatus;

constructor(
private readonly dialogRef: MatDialogRef<OrderDetailDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: NetworkAppOrderStatus
) {
this.orderStatus = data;
}

ok() {
this.dialogRef.close(this.orderStatus.id);
}

cancel() {
this.dialogRef.close();
}
}
2 changes: 2 additions & 0 deletions src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ExportPrivateKeyModalComponent } from './export-private-key-modal/expor
import { MaterialModule } from './material/material.module';
import { MediaComponent } from './media/component/media.component';
import { MigratingDialogComponent } from './migration/migrating-dialog/migrating-dialog.component';
import { OrderDetailDialogComponent } from './order-detail-dialog/order-detail-dialog.component';
import { StartsWithPipe } from './pipes/starts-with/starts-with.pipe';

const declarations = [
Expand All @@ -28,6 +29,7 @@ const declarations = [
ContactSelectionDialogComponent,
FriendInvitationDialogComponent,
ExportPrivateKeyModalComponent,
OrderDetailDialogComponent,
];

const imports = [
Expand Down
Loading