Skip to content

Commit f36275d

Browse files
committed
refactor: replace qli CurrentIpoBids with aggregation API
Replace the old /Wallet/CurrentIpoBids endpoint (qubic.li) with the new /aggregation/v1/getCurrentIpoBids endpoint on a dedicated aggregation service. Create ApiAggregationService following the one-service-per-API-tree convention. Add error handling with localized messages for bids load failures and simplify the IPO bid status icon to a hardcoded trx-executed since IPO bids are always SC calls (inputType=1, amount=0).
1 parent 65ec945 commit f36275d

File tree

19 files changed

+116
-59
lines changed

19 files changed

+116
-59
lines changed

src/app/ipo/ipo.component.html

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,51 +79,51 @@ <h3>{{ t("ipoComponent.yourShares") }}</h3>
7979
<div>
8080
<h3>{{ t("ipoComponent.yourBids") }}</h3>
8181

82-
<ng-container *ngIf="getContractBids(p.index) as bids">
82+
<div *ngIf="ipoBidsLoadError" class="contract-bid-error">
83+
<mat-icon class="error-icon">warning</mat-icon>
84+
<span>{{ t("ipoComponent.bidsLoadError") }}</span>
85+
</div>
86+
<ng-container *ngIf="!ipoBidsLoadError && getContractBids(p.index) as bids">
8387
<div *ngIf="bids.length === 0" class="empty-state">
8488
{{ t("ipoComponent.noBids") }}
8589
</div>
8690

8791
<div *ngFor="let transaction of bids" class="transaction-list">
88-
<span class="status-icon">
89-
<mat-icon *ngIf="transaction.status == 'Success'" class="trx-executed"
90-
matTooltip="{{ t('balanceComponent.transactions.status.executed.tooltip') }}">check_circle_outline</mat-icon>
91-
<mat-icon *ngIf="transaction.status == 'Failed'" class="transfer-failed"
92-
matTooltip="{{ t('balanceComponent.transactions.status.failure.tooltip') }}">highlight_off</mat-icon>
93-
<mat-icon *ngIf="transaction.status != 'Success' && transaction.status != 'Failed'" class="trx-pending"
94-
matTooltip="{{ t('balanceComponent.transactions.status.pending.tooltip') }}">schedule</mat-icon>
92+
<!-- IPO bids are SC calls (inputType=1, amount=0) so status is always 'trx-executed' -->
93+
<span class="status-icon" [matTooltip]="t('balanceComponent.transactions.status.executed.tooltip')">
94+
<mat-icon class="trx-executed">check_circle_outline</mat-icon>
9595
</span>
9696
<div class="center">
97-
<div class="trans-amount currency-value">{{ transaction.price | number: '1.0-0' }}
97+
<div class="trans-amount currency-value">{{ transaction.bid.price | number: '1.0-0' }}
9898
{{
99-
t("general.currency") }} / {{ transaction.quantity | number: '1.0-0' }} {{
99+
t("general.currency") }} / {{ transaction.bid.quantity | number: '1.0-0' }} {{
100100
t("ipoComponent.bidStatus.pcs") }} </div>
101-
<div class="trans-date"> Tick: {{ transaction.targetTick | number: '1.0-0' }} </div>
101+
<div class="trans-date"> Tick: {{ transaction.tickNumber | number: '1.0-0' }} </div>
102102
</div>
103103
<div class="address-group">
104104
<div class="send-receive-indicator">
105105
<mat-icon class="send"
106106
matTooltip="{{ t('balanceComponent.transactions.send.tooltip') }}"
107-
*ngIf="isOwnId(transaction.sourceId)">arrow_downward</mat-icon>
107+
*ngIf="isOwnId(transaction.source)">arrow_downward</mat-icon>
108108
<mat-icon class="receive"
109109
matTooltip="{{ t('balanceComponent.transactions.receive.tooltip') }}"
110-
*ngIf="isOwnId(transaction.destId)">arrow_upward</mat-icon>
110+
*ngIf="isOwnId(transaction.destination)">arrow_upward</mat-icon>
111111
</div>
112112
<div>
113-
<div [class]="{copy: true, ownId: isOwnId(transaction.sourceId)}">
113+
<div [class]="{copy: true, ownId: isOwnId(transaction.source)}">
114114
From: <span matTooltip="{{ t('general.copy.tooltip') }}"
115-
[cdkCopyToClipboard]="transaction.sourceId">{{
116-
getAddressDisplayName(transaction.sourceId) }}</span>
115+
[cdkCopyToClipboard]="transaction.source">{{
116+
getAddressDisplayName(transaction.source) }}</span>
117117
</div>
118-
<div [class]="{copy: true, ownId: isOwnId(transaction.destId)}">
118+
<div [class]="{copy: true, ownId: isOwnId(transaction.destination)}">
119119
To: <span matTooltip="{{ t('general.copy.tooltip') }}"
120-
[cdkCopyToClipboard]="transaction.destId">{{
121-
getAddressDisplayName(transaction.destId) }}</span>
120+
[cdkCopyToClipboard]="transaction.destination">{{
121+
getAddressDisplayName(transaction.destination) }}</span>
122122
</div>
123123
</div>
124124
</div>
125125
<div class="trans-dates">
126-
<span *ngIf="transaction.created">{{ transaction.created | dateTime }}</span>
126+
<span *ngIf="transaction.timestamp">{{ +transaction.timestamp | dateTime }}</span>
127127
</div>
128128
</div>
129129
</ng-container>

src/app/ipo/ipo.component.ts

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Component, OnDestroy, OnInit } from '@angular/core';
22
import { ApiService } from '../services/api.service';
33
import { WalletService } from '../services/wallet.service';
4-
import { ContractDto, IpoBid, IpoBidOverview, Transaction } from '../services/api.model';
4+
import { ContractDto, IpoBid, IpoBidOverview } from '../services/api.model';
55
import { Router } from '@angular/router';
66
import { catchError, forkJoin, of, Subject } from 'rxjs';
77
import { map, switchMap, takeUntil } from 'rxjs/operators';
88
import { AddressNameService } from '../services/address-name.service';
99
import { getDisplayName } from '../utils/address.utils';
1010
import { ApiLiveService } from '../services/apis/live/api.live.service';
1111
import { IpoBidsResponse } from '../services/apis/live/api.live.model';
12-
import { QubicStaticService } from '../services/apis/static/qubic-static.service';
13-
import { StaticSmartContract } from '../services/apis/static/qubic-static.model';
12+
import { CurrentIpoBidsContract } from '../services/apis/aggregation/api.aggregation.model';
13+
import { ApiAggregationService } from '../services/apis/aggregation/api.aggregation.service';
1414
import { ExplorerUrlHelper } from '../services/explorer-url.helper';
1515

1616
@Component({
@@ -27,15 +27,15 @@ export class IpoComponent implements OnInit, OnDestroy {
2727
public loadError: boolean = false;
2828
public failedBidContracts = new Set<number>();
2929
public retryingContracts = new Set<number>();
30-
public ipoBids: Transaction[] = [];
31-
private contractAddressMap = new Map<number, string>();
30+
public ipoBids: CurrentIpoBidsContract[] = [];
31+
public ipoBidsLoadError: boolean = false;
3232
private destroy$ = new Subject<void>();
3333

3434
constructor(
3535
private router: Router,
3636
private api: ApiService,
3737
private apiLiveService: ApiLiveService,
38-
private qubicStaticService: QubicStaticService,
38+
private apiAggregationService: ApiAggregationService,
3939
private walletService: WalletService,
4040
private addressNameService: AddressNameService
4141
) { }
@@ -56,17 +56,18 @@ export class IpoComponent implements OnInit, OnDestroy {
5656
init() {
5757
this.refreshing = true;
5858
this.loadError = false;
59+
this.ipoBidsLoadError = false;
5960
this.failedBidContracts.clear();
6061
this.retryingContracts.clear();
6162

6263
// Step 1: Get active IPOs, then fetch bids for each in parallel
6364
this.apiLiveService.getActiveIpos().pipe(
6465
switchMap(activeIpos => {
6566
if (activeIpos.length === 0) {
66-
return of({ activeIpos, bidResponses: [] as IpoBidsResponse[], bids: [] as Transaction[], staticContracts: [] as StaticSmartContract[] });
67+
return of({ activeIpos, bidResponses: [] as IpoBidsResponse[], bids: [] as CurrentIpoBidsContract[] });
6768
}
6869

69-
// Fetch bids, user transactions, and contract addresses in parallel
70+
// Fetch bids and user bid transactions in parallel
7071
const bidRequests = activeIpos.map(ipo =>
7172
this.apiLiveService.getIpoBids(ipo.contractIndex).pipe(
7273
catchError(() => {
@@ -77,21 +78,19 @@ export class IpoComponent implements OnInit, OnDestroy {
7778
);
7879
return forkJoin({
7980
bidResponses: forkJoin(bidRequests),
80-
bids: this.api.getCurrentIpoBids(this.getSeeds().map(m => m.publicId)),
81-
staticContracts: this.qubicStaticService.getSmartContracts()
81+
bids: this.apiAggregationService.getCurrentIpoBids(this.getSeeds().map(m => m.publicId)).pipe(
82+
catchError(() => {
83+
this.ipoBidsLoadError = true;
84+
return of([] as CurrentIpoBidsContract[]);
85+
})
86+
),
8287
}).pipe(
8388
map(result => ({ activeIpos, ...result }))
8489
);
8590
}),
8691
takeUntil(this.destroy$)
8792
).subscribe({
8893
next: (result) => {
89-
// Build contract address map for matching bids to contracts
90-
this.contractAddressMap.clear();
91-
for (const sc of result.staticContracts) {
92-
this.contractAddressMap.set(sc.contractIndex, sc.address);
93-
}
94-
9594
// Map live API responses to existing ContractDto format
9695
this.ipoContracts = result.activeIpos.map((ipo, i) => {
9796
const bidResponse = result.bidResponses[i];
@@ -155,14 +154,8 @@ export class IpoComponent implements OnInit, OnDestroy {
155154
}
156155

157156
getContractBids(contractId: number) {
158-
const destination = this.contractAddressMap.get(contractId);
159-
if (!destination) {
160-
return [];
161-
}
162-
163-
return this.ipoBids.filter(
164-
(tx: Transaction) => tx.destId === destination
165-
);
157+
const contract = this.ipoBids.find(c => c.contractIndex === contractId);
158+
return contract?.transactions || [];
166159
}
167160

168161
hasBidError(contractIndex: number): boolean {

src/app/services/api.service.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,6 @@ export class ApiService {
125125
);
126126
}
127127

128-
public getCurrentIpoBids(publicIds: string[]) {
129-
let localVarPath = `/Wallet/CurrentIpoBids`;
130-
return this.httpClient.request<Transaction[]>('post', `${this.basePath}${localVarPath}`,
131-
{
132-
context: new HttpContext(),
133-
headers: {
134-
"Content-Type": "application/json"
135-
},
136-
body: publicIds,
137-
responseType: 'json'
138-
}
139-
);
140-
}
141-
142128
public submitTransaction(submitTransaction: SubmitTransactionRequest) {
143129
let localVarPath = `/Public/SubmitTransaction`;
144130
return this.httpClient.request<SubmitTransactionResponse>('post', `${this.basePath}${localVarPath}`,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// /aggregation/v1/getCurrentIpoBids
2+
export interface IpoBidInfo {
3+
price: string;
4+
quantity: number;
5+
}
6+
7+
export interface IpoBidTransaction {
8+
hash: string;
9+
amount: string;
10+
source: string;
11+
destination: string;
12+
tickNumber: number;
13+
timestamp: string;
14+
inputType: number;
15+
inputSize: number;
16+
inputData: string;
17+
signature: string;
18+
moneyFlew: boolean;
19+
bid: IpoBidInfo;
20+
}
21+
22+
export interface CurrentIpoBidsContract {
23+
assetName: string;
24+
contractIndex: number;
25+
contractAddress: string;
26+
transactions: IpoBidTransaction[];
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Injectable } from '@angular/core';
2+
3+
import { CurrentIpoBidsContract } from './api.aggregation.model';
4+
import { HttpClient, HttpContext } from '@angular/common/http';
5+
import { environment } from '../../../../environments/environment';
6+
import { map, of } from 'rxjs';
7+
8+
@Injectable({
9+
providedIn: 'root'
10+
})
11+
12+
export class ApiAggregationService {
13+
private basePath = environment.aggregationApiUrl + "/aggregation/v1";
14+
15+
constructor(protected httpClient: HttpClient) {
16+
}
17+
18+
public getCurrentIpoBids(identities: string[]) {
19+
if (identities.length === 0) {
20+
return of([] as CurrentIpoBidsContract[]);
21+
}
22+
23+
let localVarPath = `/getCurrentIpoBids`;
24+
return this.httpClient.request<CurrentIpoBidsContract[]>('post', `${this.basePath}${localVarPath}`,
25+
{
26+
context: new HttpContext(),
27+
headers: {
28+
"Content-Type": "application/json"
29+
},
30+
body: { identities },
31+
responseType: 'json'
32+
}
33+
).pipe(
34+
map((response: CurrentIpoBidsContract[]) => {
35+
return response || [];
36+
})
37+
);
38+
}
39+
}

src/app/services/apis/live/api.live.service.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const qHelper = new QubicHelper();
3434

3535
export class ApiLiveService {
3636
private basePath = environment.apiUrl + "/live/v1";
37-
3837
constructor(protected httpClient: HttpClient, private walletService: WalletService) {
3938
}
4039

src/assets/i18n/cn.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@
481481
"noIpos": "目前没有 IPO 活跃",
482482
"loadError": "加载 IPO 数据失败。请重试。",
483483
"bidLoadError": "无法加载此合约的竞标数据。",
484+
"bidsLoadError": "无法加载您的竞标。请尝试刷新。",
484485
"buttons.retryBids": "重试",
485486
"yourBids": "您的出价",
486487
"yourShares": "您的股份",

src/assets/i18n/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@
480480
"noIpos": "Aktuell keine IPOs aktiv",
481481
"loadError": "IPO-Daten konnten nicht geladen werden. Bitte versuchen Sie es erneut.",
482482
"bidLoadError": "Gebotsdaten für diesen Vertrag konnten nicht geladen werden.",
483+
"bidsLoadError": "Ihre Gebote konnten nicht geladen werden. Bitte versuchen Sie es erneut.",
483484
"buttons.retryBids": "Erneut versuchen",
484485
"yourBids": "Ihre Gebote",
485486
"yourShares": "Ihre Anteile",

src/assets/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@
483483
"noIpos": "Currently no IPO's active",
484484
"loadError": "Failed to load IPO data. Please try again.",
485485
"bidLoadError": "Failed to load bid data for this contract.",
486+
"bidsLoadError": "Failed to load your bids. Please try refreshing.",
486487
"buttons.retryBids": "Retry",
487488
"yourBids": "Your Bids",
488489
"yourShares": "Your Shares",

src/assets/i18n/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@
479479
"noIpos": "Actualmente no hay IPOs activas",
480480
"loadError": "No se pudieron cargar los datos de la IPO. Por favor, inténtelo de nuevo.",
481481
"bidLoadError": "No se pudieron cargar los datos de ofertas para este contrato.",
482+
"bidsLoadError": "No se pudieron cargar sus ofertas. Por favor, intente actualizar.",
482483
"buttons.retryBids": "Reintentar",
483484
"yourBids": "Tus Ofertas",
484485
"yourShares": "Tus Acciones",

0 commit comments

Comments
 (0)