Skip to content

Commit a36b9d3

Browse files
sonasijdvlioIscoRuta98billguo99jongbonga
authored
Merge autofund wait message to demo (#501)
* refactor(backend/tests): remove superfluous mocks (#473) * refactor(backend): use odmantic engine (#479) * rename(backend/setting): maximum list length * feat(backend): improve `GET` endpoint semantics The endpoint should receive the `wallet_id` as a query parameter and return the internal mongodb document id. * chore(backend): remove dead code * refactor(backend): use odmantic engine * fix(web-client): add issuers for servers (#482) * fix(web-client): add issuers for servers * Update web-client/src/environments/environment.palau-prod.ts Co-authored-by: Jean-Pierre de Villiers <[email protected]> Co-authored-by: Jean-Pierre de Villiers <[email protected]> * feat(web-client): Pull payment flag (#486) * feat(web-client): Add hidePullPayment flag. * style(web-client): run prettier * fix(web-client): Fix ESLint error. * style(web-client): run prettier * fix(web-client): Change auth_map type to Object type. (#485) * fix(web-client): Change auth_map type to Object type. * fix(web-client): Fix ESLint error. * feat(web-client): add auto logout (#481) * feat(web-client): add auto logout * feat(web-client): add ngZone for auto logout * refactor(web-client): remove redundant isLogin variable in auto logout * refactor(web-client): move auto logout to app component * refactor(web-client): remove autoLogOutService from logout-button * refactor(web-client): remove console log line in auto-logout service * feat(web-client): add environment flag for auto logout * fix(web-client): initialize localStorage lastAction for auto logout first * fix(web-client): pull payment minor text fixes (#491) * fix(cors): allow `localhost:4200` on staging (#492) * feat(web-client): bookmark management (#451) * fix(web-client): Update Pin Entry text for pull payment. (#497) * fix(web-client): Update Pin Entry text for pull payment. * style(web-client): run prettier. * fix(web-client): update prod feature flags (#499) * fix(web-client): update prod feature flags * fix(web-client): update prod feature flags * feat(web-client): Add transaction history (#468) * fix(web-client): display wait message while creating wallet. (#493) * fix(web-client): display wait message and error messages while creating wallet. * fix(web-client): clean up comments and other code. * fix(web-client) add routertesting to asset accordian * fix(web-client) fix txresponse import format and message html and text fields. * fix(web-client) run prettier. Co-authored-by: Jean-Pierre de Villiers <[email protected]> Co-authored-by: Francisco Rutayebesibwa <[email protected]> Co-authored-by: Bill Guo <[email protected]> Co-authored-by: Jonathan Ngbonga <[email protected]>
1 parent cac33bf commit a36b9d3

24 files changed

+429
-20
lines changed

web-client/capacitor.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CapacitorConfig } from '@capacitor/cli';
2+
import { KeyboardResize } from '@capacitor/keyboard';
23

34
const config: CapacitorConfig = {
45
appId: 'io.ionic.starter',
@@ -11,7 +12,7 @@ const config: CapacitorConfig = {
1112
androidScaleType: 'CENTER_CROP',
1213
},
1314
Keyboard: {
14-
resize: 'ionic',
15+
resize: KeyboardResize.Ionic,
1516
},
1617
},
1718
};

web-client/src/app/app-routing.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ export const routes: Routes = [
120120
(m) => m.SearchWalletPageModule
121121
),
122122
},
123+
{
124+
path: 'history',
125+
loadChildren: () =>
126+
import('./views/history/history.module').then((m) => m.HistoryPageModule),
127+
},
123128
{
124129
path: 'bookmarks',
125130
loadChildren: () =>

web-client/src/app/components/asset-accordion/asset-accordion.component.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { HttpClientTestingModule } from '@angular/common/http/testing';
22
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3+
import { RouterTestingModule } from '@angular/router/testing';
34
import { IonicModule } from '@ionic/angular';
45
import { AssetAccordionComponent } from './asset-accordion.component';
56

@@ -10,7 +11,11 @@ describe('AssetAccordionComponent', () => {
1011
beforeEach(waitForAsync(() => {
1112
TestBed.configureTestingModule({
1213
declarations: [AssetAccordionComponent],
13-
imports: [IonicModule.forRoot(), HttpClientTestingModule],
14+
imports: [
15+
IonicModule.forRoot(),
16+
HttpClientTestingModule,
17+
RouterTestingModule,
18+
],
1419
}).compileComponents();
1520

1621
fixture = TestBed.createComponent(AssetAccordionComponent);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { SessionQuery } from '../state/session.query';
3+
import { IsMePipe } from './is-me.pipe';
4+
5+
describe('IsMePipe', () => {
6+
it('create an instance', () => {
7+
const sessionQuery: SessionQuery = TestBed.get(SessionQuery);
8+
const pipe = new IsMePipe(sessionQuery);
9+
expect(pipe).toBeTruthy();
10+
});
11+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
import { SessionQuery } from '../state/session.query';
3+
4+
@Pipe({
5+
name: 'isMe',
6+
})
7+
export class IsMePipe implements PipeTransform {
8+
constructor(private sessionQuery: SessionQuery) {}
9+
10+
transform(address: string | undefined): boolean {
11+
const me = this.sessionQuery.getValue().wallet?.wallet_id;
12+
return address === me;
13+
}
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { XrpDatePipe } from './xrp-date.pipe';
2+
3+
describe('XrpDatePipe', () => {
4+
it('create an instance', () => {
5+
const pipe = new XrpDatePipe();
6+
expect(pipe).toBeTruthy();
7+
});
8+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
3+
@Pipe({
4+
name: 'xrpDate',
5+
})
6+
export class XrpDatePipe implements PipeTransform {
7+
transform(date: number | undefined): string {
8+
if (date) {
9+
const from = new Date('01/01/2000');
10+
const finalDate = new Date(from.setSeconds(from.getSeconds() + date));
11+
return new Date(finalDate).toLocaleString();
12+
}
13+
return '';
14+
}
15+
}

web-client/src/app/services/xrpl.service.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,21 @@ export class XrplService {
181181
);
182182
}
183183

184+
/**
185+
* Retrieves a list of transactions that involved the specified account
186+
*
187+
* @see https://js.xrpl.org/interfaces/AccountTxRequest.html
188+
*/
189+
async getAccountTx(account: string): Promise<xrpl.AccountTxResponse> {
190+
return await this.withConnection(
191+
async (client) =>
192+
await client.request({
193+
command: 'account_tx',
194+
account,
195+
})
196+
);
197+
}
198+
184199
// For Reference: https://github.com/XRPLF/xrpl.js/blob/6e4868e6c7a03f0d48de1ddee5d9a88700ab5a7c/src/transaction/sign.ts#L54
185200
/*
186201
async submitTransaction(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Transaction, TransactionMetadata } from 'xrpl';
2+
3+
export interface History {
4+
meta: string | TransactionMetadata;
5+
tx?: Transaction & {
6+
Destination?: string;
7+
Amount?: {
8+
currency?: string;
9+
value?: string;
10+
};
11+
date?: number;
12+
hash?: string;
13+
ledger_index?: number;
14+
};
15+
validated: boolean;
16+
}
17+
18+
export const createHistory = (params: Partial<History>) => ({} as History);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Injectable } from '@angular/core';
2+
import { QueryEntity } from '@datorama/akita';
3+
import { SetupQuery } from '../setup';
4+
import { HistoryState, HistoryStore } from './history.store';
5+
6+
@Injectable({ providedIn: 'root' })
7+
export class HistoryQuery extends QueryEntity<HistoryState> {
8+
transactions = this.selectAll({
9+
filterBy: ({ tx }: any) =>
10+
tx?.TransactionType === 'Payment' &&
11+
tx.Amount?.currency === this.setupQuery.tokenSymbol,
12+
});
13+
14+
constructor(protected store: HistoryStore, private setupQuery: SetupQuery) {
15+
super(store);
16+
}
17+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Injectable } from '@angular/core';
2+
import { XrplService } from 'src/app/services/xrpl.service';
3+
import { SessionQuery } from '../session.query';
4+
import { HistoryStore } from './history.store';
5+
6+
@Injectable({ providedIn: 'root' })
7+
export class HistoryService {
8+
constructor(
9+
private historyStore: HistoryStore,
10+
private xrplService: XrplService,
11+
private sessionQuery: SessionQuery
12+
) {}
13+
14+
async getTxList() {
15+
const address = this.sessionQuery.getValue().wallet?.wallet_id;
16+
if (address) {
17+
await this.xrplService.getAccountTx(address).then(({ result }) => {
18+
const { transactions } = result;
19+
this.historyStore.set(transactions);
20+
});
21+
}
22+
}
23+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Injectable } from '@angular/core';
2+
import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
3+
import { History } from './history.model';
4+
5+
export type HistoryState = EntityState<History>;
6+
7+
@Injectable({ providedIn: 'root' })
8+
@StoreConfig({
9+
name: 'history',
10+
idKey: 'date',
11+
resettable: true,
12+
})
13+
export class HistoryStore extends EntityStore<HistoryState> {
14+
constructor() {
15+
super();
16+
}
17+
18+
akitaPreAddEntity(
19+
newEntity: History
20+
): History & { date: number | undefined } {
21+
return {
22+
...newEntity,
23+
date: newEntity.tx?.date,
24+
};
25+
}
26+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './history.model';
2+
export * from './history.query';
3+
export * from './history.service';
4+
export { HistoryState, HistoryStore } from './history.store';

web-client/src/app/state/session-xrpl.service.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { HttpClientTestingModule } from '@angular/common/http/testing';
22
import { TestBed } from '@angular/core/testing';
3+
import { RouterTestingModule } from '@angular/router/testing';
34
import { SessionXrplService } from './session-xrpl.service';
45
import { SessionStore } from './session.store';
56

@@ -9,7 +10,7 @@ describe('SessionXrplService', () => {
910

1011
beforeEach(() => {
1112
TestBed.configureTestingModule({
12-
imports: [HttpClientTestingModule],
13+
imports: [HttpClientTestingModule, RouterTestingModule],
1314
providers: [SessionXrplService, SessionStore],
1415
});
1516

web-client/src/app/state/session-xrpl.service.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Injectable } from '@angular/core';
2+
import { NavController } from '@ionic/angular';
23
import { firstValueFrom } from 'rxjs';
34
import { EnclaveService } from 'src/app/services/enclave';
45
import { XrplService } from 'src/app/services/xrpl.service';
@@ -27,7 +28,6 @@ import { SwalHelper } from '../utils/notification/swal-helper';
2728
import { ConnectorQuery } from './connector';
2829
import { SessionQuery } from './session.query';
2930
import { SessionStore, XrplBalance } from './session.store';
30-
3131
/**
3232
* This service manages session state and operations related to the XRP ledger.
3333
*/
@@ -68,7 +68,7 @@ export class SessionXrplService {
6868
private enclaveService: EnclaveService,
6969
private xrplService: XrplService,
7070
private connectorQuery: ConnectorQuery,
71-
71+
private navCtrl: NavController,
7272
private notification: SwalHelper
7373
) {}
7474

@@ -139,7 +139,7 @@ export class SessionXrplService {
139139
async sendAutoFunds(
140140
receiverId: string,
141141
amount: number
142-
): Promise<xrpl.TxResponse> {
142+
): Promise<{ xrplResult: xrpl.TxResponse }> {
143143
const public_key_hex = environment.autofundXrpPublicKey;
144144
const issuer_id = environment.xrpIssuer;
145145
const pin = environment.autofundAccountPin;
@@ -161,9 +161,8 @@ export class SessionXrplService {
161161
);
162162

163163
const txResponse = await this.submitTransaction(txnSignedEncoded);
164-
const txSucceeded = checkTxResponseSucceeded(txResponse);
165164

166-
return txResponse;
165+
return { xrplResult: txResponse };
167166
}
168167

169168
async sendFundsCommissioned(
@@ -555,10 +554,16 @@ export class SessionXrplService {
555554
err instanceof Error &&
556555
err.message.includes('SessionService.signTransaction: invalid auth')
557556
) {
558-
await this.notification.swal.fire({
559-
icon: 'error',
560-
text: 'Invalid PIN',
561-
});
557+
await this.notification.swal
558+
.fire({
559+
icon: 'error',
560+
text: 'Invalid PIN',
561+
})
562+
.then(({ isConfirmed }) => {
563+
if (isConfirmed) {
564+
this.navCtrl.navigateRoot('/');
565+
}
566+
});
562567
throw new Error('Local error:');
563568
} else {
564569
throw err;

web-client/src/app/state/setup/setup.query.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SetupState, SetupStore } from './setup.store';
77
export class SetupQuery extends Query<SetupState> {
88
tokenIssuer = this.getValue().tokenIssuer.trim() || environment.tokenIssuer;
99
xrpIssuer = this.getValue().xrpIssuer.trim() || environment.xrpIssuer;
10+
tokenSymbol = this.getValue().tokenSymbol.trim() || environment.tokenSymbol;
1011
ledger = this.getValue().ledger.trim()
1112
? { ...environment.xrplClient, server: this.getValue().ledger }
1213
: environment.xrplClient;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { NgModule } from '@angular/core';
2+
import { RouterModule, Routes } from '@angular/router';
3+
import { HistoryPage } from './history.page';
4+
5+
const routes: Routes = [
6+
{
7+
path: '',
8+
component: HistoryPage,
9+
},
10+
];
11+
12+
@NgModule({
13+
imports: [RouterModule.forChild(routes)],
14+
exports: [RouterModule],
15+
})
16+
export class HistoryPageRoutingModule {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { CommonModule } from '@angular/common';
2+
import { NgModule } from '@angular/core';
3+
import { FormsModule } from '@angular/forms';
4+
import { IonicModule } from '@ionic/angular';
5+
import { SharedModule } from 'src/app/modules/shared/shared.module';
6+
import { IsMePipe } from 'src/app/pipes/is-me.pipe';
7+
import { XrpDatePipe } from 'src/app/pipes/xrp-date.pipe';
8+
import { HistoryPageRoutingModule } from './history-routing.module';
9+
import { HistoryPage } from './history.page';
10+
11+
@NgModule({
12+
imports: [
13+
CommonModule,
14+
FormsModule,
15+
IonicModule,
16+
HistoryPageRoutingModule,
17+
SharedModule,
18+
],
19+
declarations: [HistoryPage, XrpDatePipe, IsMePipe],
20+
})
21+
export class HistoryPageModule {}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<ion-header>
2+
<ion-toolbar>
3+
<ion-buttons slot="start">
4+
<ion-back-button></ion-back-button>
5+
</ion-buttons>
6+
<ion-title>History</ion-title>
7+
<ion-buttons slot="end">
8+
<app-logout-button></app-logout-button>
9+
</ion-buttons>
10+
</ion-toolbar>
11+
</ion-header>
12+
13+
<ion-content>
14+
<div class="ion-padding">
15+
<h2 class="font-bold">Recent transactions</h2>
16+
</div>
17+
18+
<ng-container *ngIf="(historyQuery.transactions| async)?.length; else empty">
19+
<ion-list lines="full" *ngIf="historyQuery.transactions| async">
20+
<ion-item *ngFor="let item of historyQuery.transactions| async">
21+
<ion-icon
22+
slot="start"
23+
[name]="(item?.tx?.Account | isMe) ? 'arrow-forward' : 'arrow-back'"
24+
></ion-icon>
25+
<ion-label>
26+
<ion-text class="font-bold" color="primary">
27+
<small>
28+
{{(item?.tx?.Account | isMe) ? 'Sent To' : 'Received From'}}
29+
</small>
30+
</ion-text>
31+
<h2>
32+
{{(item?.tx?.Account | isMe) ? item?.tx?.Destination :
33+
item?.tx?.Account}}
34+
</h2>
35+
<h6>{{item?.tx?.date | xrpDate}}</h6>
36+
</ion-label>
37+
<ion-note slot="end" color="primary">
38+
<small class="font-bold">
39+
{{item?.tx?.Amount?.value | currency: 'FOO'}}
40+
</small>
41+
</ion-note>
42+
</ion-item>
43+
</ion-list>
44+
</ng-container>
45+
46+
<ng-template #empty>
47+
<div class="ion-padding-horizontal ion-text-center">
48+
<p>Your transactions list is empty</p>
49+
</div>
50+
</ng-template>
51+
</ion-content>

web-client/src/app/views/history/history.page.scss

Whitespace-only changes.

0 commit comments

Comments
 (0)