diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html new file mode 100644 index 000000000..d86d32a50 --- /dev/null +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -0,0 +1,324 @@ +
+
+ @if (isMobile) { +
+ + + + + +
+
+ } @else { +
+ + + + +
+
+
+ + + + +
+
+ } +
+
+ + + @if (tx?.status?.confirmed) { + + + } @else { + + + } + + @if (tx?.status?.confirmed) { + + } + + + + + + + @if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration && ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo)) { + + } @else { + + } + @if (tx?.status?.confirmed) { + + } + + + + @if (!isLoadingTx) { + + Timestamp + + ‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm:ss' }} +
+ () +
+ + + } @else { + + } +
+ + + @if (!isLoadingTx) { + @if (transactionTime > 0) { + + Confirmed + + + } + } @else { + + } + + + + @if (isLoadingTx) { + + } @else if (transactionTime > 0) { + + First seen + + + } @else if (isLoadingFirstSeen) { + + First seen + + + } + + + + @if (network !== 'liquid' && network !== 'liquidtestnet') { + @if (!isLoadingTx) { + @if (featuresEnabled) { + + Features + + + + + } + } @else { + + } + } + + + + @if (network === '') { + @if (!isLoadingTx) { + @if (auditStatus) { + + Audit + + + @if (auditStatus.coinbase) { + Coinbase + } @else if (auditStatus.expected) { + Expected in Block + } @else if (auditStatus.seen) { + Seen in Mempool + } @else if (!auditStatus.conflict) { + Not seen in Mempool + } + @if (auditStatus.added) { + Added + } + @if (auditStatus.prioritized) { + Prioritized + } + @if (auditStatus.conflict) { + Conflict + } + + + + } + } @else { + + } + } + + + + @if (!isLoadingTx) { + @if (!replaced && !isCached) { + + ETA + + + @if (network === 'liquid' || network === 'liquidtestnet') { + + } @else { + + @if (eta.blocks >= 7) { + Not any time soon + } @else { + + } + @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) { + + } + + } + + + + + + + } + } @else { + + } + + + + @if (!isLoadingTx) { + @if (isAcceleration || filters.length) { + + + + + + @if (isAcceleration) { + Accelerated + } + + {{ filter.label }} + + + + } + } + + + + @if (!isLoadingTx) { + + Fee + {{ tx.fee | number }} sats + @if (accelerationInfo?.bidBoost ?? tx.feeDelta > 0) { + +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} sats + } + + + + } @else { + + } + + + + @if (!isLoadingTx) { + + Fee rate + + + @if (tx?.status?.confirmed && tx.fee && !hasEffectiveFeeRate && !accelerationInfo) { +   + + } + + + } @else { + + } + + + + @if (!isLoadingTx) { + @if ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo) { + + @if (isAcceleration) { + Accelerated fee rate + } @else { + Effective fee rate + } + +
+ @if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize || tx.acceleration)) { + + } @else { + + } + + @if (tx?.status?.confirmed && !tx.acceleration && !accelerationInfo && tx.fee && tx.effectiveFeePerVsize) { + + } +
+ @if (hasCpfp) { + + } + + + } + } @else { + + } +
+ + + + + + + + + + + + @if (network === '') { + @if (!isLoadingTx) { + + Miner + @if (pool) { + + + + @if (pool.minerNames[1].length > 16) { + {{ pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ pool.minerNames[1] }} + } + + + {{ pool.name }} + + + } @else { + + + + } + + } @else { + + } + } + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss new file mode 100644 index 000000000..9bb32ba4a --- /dev/null +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss @@ -0,0 +1,183 @@ +.title-block { + flex-wrap: wrap; + align-items: baseline; + @media (min-width: 650px) { + flex-direction: row; + } + h1 { + margin: 0rem; + margin-right: 15px; + line-height: 1; + } +} + +.td-width { + width: 150px; + + @media (max-width: 768px) { + width: 175px; + } +} + +.badge { + position: relative; + top: -1px; +} + +.miner-name { + margin-right: 4px; + vertical-align: top; +} + +.pool-logo { + width: 25px; + height: 25px; + position: relative; + top: -1px; + margin-right: 2px; +} + +.badge.badge-accelerated { + background-color: var(--tertiary); + color: white; +} + +.btn-small-height { + line-height: 1; +} + +.row{ + flex-direction: column; + @media (min-width: 850px) { + flex-direction: row; + } +} + +.box.hidden { + visibility: hidden; + height: 0px; + padding-top: 0px; + padding-bottom: 0px; + margin-top: 0px; + margin-bottom: 0px; +} + +@media (max-width: 767.98px) { + .mobile-bottomcol { + margin-top: 15px; + } + + .details-table td:first-child { + white-space: pre-wrap; + } +} + +.fiat { + display: block; + @media (min-width: 768px){ + display: inline-block; + margin-left: 15px; + text-align: left; + } +} + +.table { + tr td { + padding: 0.75rem 0.5rem; + @media (min-width: 576px) { + padding: 0.75rem 0.75rem; + } + &:last-child { + text-align: right; + @media (min-width: 850px) { + text-align: left; + } + } + .btn { + display: block; + } + + &.wrap-cell { + white-space: normal; + } + } +} + +.effective-fee-container { + display: block; + @media (min-width: 768px){ + display: inline-block; + } + @media (max-width: 425px){ + display: flex; + flex-direction: column; + } +} + +@media (max-width: 767px){ + .hide-on-mobile { + display: none; + } +} + +.effective-fee-rating { + @media (max-width: 767px){ + margin-right: 0px !important; + } +} + +.btn-outline-info { + margin-top: 5px; + @media (min-width: 768px){ + margin-top: 0px; + } +} + +.eta { + display: flex; + flex-wrap: wrap; + align-content: center; + @media (min-width: 850px) { + justify-content: left !important; + } +} + +.accelerate { + @media (min-width: 850px) { + margin-left: auto; + } +} + +.etaDeepMempool { + flex-wrap: wrap; + @media (max-width: 849px) { + justify-content: right !important; + } +} + +.accelerateDeepMempool { + background-color: var(--tertiary); + margin-left: 5px; +} + +.goggles-icon { + display: block; + width: 2.7em; +} + +.pool-logo { + width: 15px; + height: 15px; + position: relative; + top: -1px; + margin-right: 2px; +} + +.oobFees { + color: #905cf4; +} + +.disabled { + opacity: 0.5; + pointer-events: none; +} diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts new file mode 100644 index 000000000..59770d97e --- /dev/null +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, Input, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core'; +import { Transaction } from '@interfaces/electrs.interface'; +import { Acceleration, CpfpInfo } from '@interfaces/node-api.interface'; +import { Pool, TxAuditStatus } from '@components/transaction/transaction.component'; +import { Observable } from 'rxjs'; +import { ETA } from '@app/services/eta.service'; +import { MiningStats } from '@app/services/mining.service'; +import { Filter } from '@app/shared/filters.utils'; + +@Component({ + selector: 'app-transaction-details', + templateUrl: './transaction-details.component.html', + styleUrls: ['./transaction-details.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransactionDetailsComponent implements OnInit { + @Input() network: string; + @Input() tx: Transaction; + @Input() isLoadingTx: boolean; + @Input() isMobile: boolean; + @Input() transactionTime: number; + @Input() isLoadingFirstSeen: boolean; + @Input() featuresEnabled: boolean; + @Input() auditStatus: TxAuditStatus; + @Input() filters: Filter[]; + @Input() miningStats: MiningStats; + @Input() pool: Pool | null; + @Input() isAcceleration: boolean; + @Input() hasEffectiveFeeRate: boolean; + @Input() cpfpInfo: CpfpInfo; + @Input() hasCpfp: boolean; + @Input() showCpfpDetails: boolean; + @Input() accelerationInfo: Acceleration; + @Input() acceleratorAvailable: boolean; + @Input() accelerateCtaType: string; + @Input() notAcceleratedOnLoad: boolean; + @Input() showAccelerationSummary: boolean; + @Input() eligibleForAcceleration: boolean; + @Input() replaced: boolean; + @Input() isCached: boolean; + @Input() ETA$: Observable; + + @Output() accelerateClicked = new EventEmitter(); + + constructor() {} + + ngOnInit(): void {} + + onAccelerateClicked(): void { + this.accelerateClicked.emit(true); + } +} diff --git a/frontend/src/app/components/transaction/transaction-extras.module.ts b/frontend/src/app/components/transaction/transaction-extras.module.ts new file mode 100644 index 000000000..ddfff51a5 --- /dev/null +++ b/frontend/src/app/components/transaction/transaction-extras.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + exports: [ + ] +}) +export class TransactionExtrasModule { } diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 056b27fc5..ecd98d979 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -31,35 +31,35 @@
@if (!error) { -
-
- @if (isMobile) { -
- - - - - -
-
- } @else { -
- - - - -
-
-
- - - - -
-
- } -
-
+ } @@ -416,299 +416,4 @@ - - - - @if (tx?.status?.confirmed) { - - - } @else { - - - } - - @if (tx?.status?.confirmed) { - - } - - - - - - - @if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration && ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo)) { - - } @else { - - } - @if (tx?.status?.confirmed) { - - } - - - - @if (!isLoadingTx) { - - Timestamp - - ‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm:ss' }} -
- () -
- - - } @else { - - } -
- - - @if (!isLoadingTx) { - @if (transactionTime > 0) { - - Confirmed - - - } - } @else { - - } - - - - @if (isLoadingTx) { - - } @else if (transactionTime > 0) { - - First seen - - - } @else if (isLoadingFirstSeen) { - - First seen - - - } - - - - @if (network !== 'liquid' && network !== 'liquidtestnet') { - @if (!isLoadingTx) { - @if (featuresEnabled) { - - Features - - - - - } - } @else { - - } - } - - - - @if (network === '') { - @if (!isLoadingTx) { - @if (auditStatus) { - - Audit - - - @if (auditStatus.coinbase) { - Coinbase - } @else if (auditStatus.expected) { - Expected in Block - } @else if (auditStatus.seen) { - Seen in Mempool - } @else if (!auditStatus.conflict) { - Not seen in Mempool - } - @if (auditStatus.added) { - Added - } - @if (auditStatus.prioritized) { - Prioritized - } - @if (auditStatus.conflict) { - Conflict - } - - - - } - } @else { - - } - } - - - - @if (!isLoadingTx) { - @if (!replaced && !isCached) { - - ETA - - - @if (network === 'liquid' || network === 'liquidtestnet') { - - } @else { - - @if (eta.blocks >= 7) { - Not any time soon - } @else { - - } - @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) { - - } - - } - - - - - - - } - } @else { - - } - - - - @if (!isLoadingTx) { - @if (isAcceleration || filters.length) { - - - - - - @if (isAcceleration) { - Accelerated - } - - {{ filter.label }} - - - - } - } - - - - @if (!isLoadingTx) { - - Fee - {{ tx.fee | number }} sats - @if (accelerationInfo?.bidBoost ?? tx.feeDelta > 0) { - +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} sats - } - - - - } @else { - - } - - - - @if (!isLoadingTx) { - - Fee rate - - - @if (tx?.status?.confirmed && tx.fee && !hasEffectiveFeeRate && !accelerationInfo) { -   - - } - - - } @else { - - } - - - - @if (!isLoadingTx) { - @if ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo) { - - @if (isAcceleration) { - Accelerated fee rate - } @else { - Effective fee rate - } - -
- @if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize || tx.acceleration)) { - - } @else { - - } - - @if (tx?.status?.confirmed && !tx.acceleration && !accelerationInfo && tx.fee && tx.effectiveFeePerVsize) { - - } -
- @if (hasCpfp) { - - } - - - } - } @else { - - } -
- - - - - - - - - - - - @if (network === '') { - @if (!isLoadingTx) { - - Miner - @if (pool) { - - - - @if (pool.minerNames[1].length > 16) { - {{ pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ pool.minerNames[1] }} - } - - - {{ pool.name }} - - - } @else { - - - - } - - } @else { - - } - } - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index 42325a1b4..7125a6b46 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -18,6 +18,7 @@ line-height: 1; } } + .tx-link { display: flex; flex-direction: row; @@ -60,19 +61,6 @@ top: -1px; } -.miner-name { - margin-right: 4px; - vertical-align: top; -} - -.pool-logo { - width: 25px; - height: 25px; - position: relative; - top: -1px; - margin-right: 2px; -} - .badge.badge-accelerated { background-color: var(--tertiary); color: white; @@ -94,7 +82,7 @@ margin-bottom: 40px; } -.row{ +.row { flex-direction: column; @media (min-width: 850px) { flex-direction: row; @@ -337,4 +325,4 @@ .disabled { opacity: 0.5; pointer-events: none; -} \ No newline at end of file +} diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index f17fc558f..5f5ef4fa7 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -38,7 +38,7 @@ import { ZONE_SERVICE } from '@app/injection-tokens'; import { MiningService, MiningStats } from '@app/services/mining.service'; import { ETA, EtaService } from '@app/services/eta.service'; -interface Pool { +export interface Pool { id: number; name: string; slug: string; diff --git a/frontend/src/app/components/transaction/transaction.module.ts b/frontend/src/app/components/transaction/transaction.module.ts index 5313b6a44..80de0cf40 100644 --- a/frontend/src/app/components/transaction/transaction.module.ts +++ b/frontend/src/app/components/transaction/transaction.module.ts @@ -2,8 +2,10 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Routes, RouterModule } from '@angular/router'; import { TransactionComponent } from '@components/transaction/transaction.component'; +import { TransactionDetailsComponent } from '@components/transaction/transaction-details/transaction-details.component'; import { SharedModule } from '@app/shared/shared.module'; import { TxBowtieModule } from '@components/tx-bowtie-graph/tx-bowtie.module'; +import { TransactionExtrasModule } from '@components/transaction/transaction-extras.module'; import { GraphsModule } from '@app/graphs/graphs.module'; import { AccelerateCheckout } from '@components/accelerate-checkout/accelerate-checkout.component'; import { AccelerateFeeGraphComponent } from '@components/accelerate-checkout/accelerate-fee-graph.component'; @@ -40,14 +42,17 @@ export class TransactionRoutingModule { } SharedModule, GraphsModule, TxBowtieModule, + TransactionExtrasModule, ], declarations: [ TransactionComponent, + TransactionDetailsComponent, AccelerateCheckout, AccelerateFeeGraphComponent, ], exports: [ TransactionComponent, + TransactionDetailsComponent, AccelerateCheckout, AccelerateFeeGraphComponent, ]