mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 14:50:52 +01:00
Merge pull request #5080 from mempool/nymkappa/faucet-polish
[faucet] polish code and UX - Move Twitter login component into FOSS
This commit is contained in:
commit
2ce1cc24b9
10 changed files with 215 additions and 147 deletions
|
@ -4,43 +4,54 @@
|
|||
<h1 i18n="testnet4.faucet">Testnet4 Faucet</h1>
|
||||
</div>
|
||||
|
||||
@if (error) {
|
||||
<div class="alert alert-danger">
|
||||
@switch (error) {
|
||||
@case ('faucet_too_soon') {
|
||||
Too many requests! Try again later.
|
||||
}
|
||||
@case ('faucet_maximum_reached') {
|
||||
You have exceeded your testnet4 allowance. Try again later.
|
||||
}
|
||||
@case ('faucet_not_available') {
|
||||
The faucet is not available right now. Try again later.
|
||||
}
|
||||
@default {
|
||||
Sorry, something went wrong! Try again later.
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="faucet-container text-center">
|
||||
|
||||
@if (txid) {
|
||||
<div class="alert alert-mempool d-block text-center">
|
||||
<a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid }}</a>
|
||||
<div class="alert alert-success w-100 text-truncate">
|
||||
<fa-icon [icon]="['fas', 'circle-check']"></fa-icon>
|
||||
Sent!
|
||||
<a class="text-primary" [href]="'/testnet4/txid/' + txid">{{ txid }}</a>
|
||||
</div>
|
||||
} @else if (loading) {
|
||||
<p>Waiting for faucet...</p>
|
||||
}
|
||||
@else if (loading) {
|
||||
<p>Loading faucet...</p>
|
||||
<div class="spinner-border text-light"></div>
|
||||
} @else {
|
||||
<form [formGroup]="faucetForm" class="formGroup" (submit)="requestCoins()">
|
||||
} @else if (!user) {
|
||||
<!-- User not logged in -->
|
||||
<div class="alert alert-mempool d-block text-center w-100">
|
||||
<div class="d-inline align-middle">
|
||||
<span>To limit abuse, </span>
|
||||
<a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">authenticate </a>
|
||||
<span class="mr-2">or</span>
|
||||
</div>
|
||||
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
|
||||
</div>
|
||||
}
|
||||
@else if (error === 'not_available') {
|
||||
<!-- User logged in but not a paid user or did not link its Twitter account -->
|
||||
<div class="alert alert-mempool d-block text-center w-100">
|
||||
<div class="d-inline align-middle">
|
||||
<span class="mb-2 mr-2">To limit abuse</span>
|
||||
</div>
|
||||
<app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login>
|
||||
</div>
|
||||
}
|
||||
|
||||
@else if (error) {
|
||||
<!-- User can request -->
|
||||
<app-mempool-error class="w-100" [error]="error"></app-mempool-error>
|
||||
}
|
||||
|
||||
@if (!loading) {
|
||||
<form [formGroup]="faucetForm" class="formGroup" (submit)="requestCoins()" [style]="(error || !this.user) ? 'opacity: 0.3; pointer-events: none' : ''">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<div class="input-group input-group-lg mb-2">
|
||||
<div class="form-group mb-0">
|
||||
<div class="input-group input-group-lg">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" i18n="amount-sats">Amount (sats)</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" formControlName="satoshis" id="satoshis">
|
||||
<input type="number" class="form-control" [class]="{invalid: invalidAmount}" formControlName="satoshis" id="satoshis">
|
||||
<div class="button-group">
|
||||
<button type="button" class="btn btn-secondary" (click)="setAmount(5000)">5k</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="setAmount(50000)">50k</button>
|
||||
|
@ -49,43 +60,32 @@
|
|||
</div>
|
||||
<div class="text-danger text-left" *ngIf="invalidAmount">
|
||||
<div *ngIf="amount?.errors?.['required']">Amount is required</div>
|
||||
<div *ngIf="status?.user_requests && amount?.errors?.['min']">Minimum is {{ amount?.errors?.['min'].min }}</div>
|
||||
<div *ngIf="status?.user_requests && amount?.errors?.['max']">Maximum is {{ amount?.errors?.['max'].max }}</div>
|
||||
<div *ngIf="amount?.errors?.['min']">Minimum is {{ amount?.errors?.['min'].min | number }} tSats</div>
|
||||
<div *ngIf="amount?.errors?.['max']">Maximum is {{ amount?.errors?.['max'].max | number }} tSats</div>
|
||||
</div>
|
||||
<div class="input-group input-group-lg mb-2">
|
||||
<div class="input-group input-group-lg mt-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" i18n="address">Address</span>
|
||||
</div>
|
||||
<input type="address" class="form-control" formControlName="address" id="address" placeholder="tb1q...">
|
||||
<button type="submit" class="btn btn-primary submit-button" [disabled]="!status?.access || !faucetForm.valid || !faucetForm.get('address')?.dirty" i18n="testnet4.request-coins">Request Testnet4 Coins</button>
|
||||
<input type="text" class="form-control" [class]="{invalid: invalidAddress}" formControlName="address" id="address" placeholder="tb1q...">
|
||||
<button type="submit" class="btn btn-primary submit-button" [disabled]="!faucetForm.valid || !faucetForm.get('address')?.dirty" i18n="testnet4.request-coins">Request Testnet4 Coins</button>
|
||||
</div>
|
||||
<div class="text-danger text-left" *ngIf="invalidAddress">
|
||||
@if (address?.errors?.['required']) {
|
||||
<div>Address is required</div>
|
||||
} @else {
|
||||
<div>Must be a valid testnet4 address</div>
|
||||
}
|
||||
</div>
|
||||
<div class="text-danger text-left" *ngIf="status && !status.user_requests">
|
||||
<div>Too many requests! Try again later.</div>
|
||||
<div *ngIf="address?.errors?.['required']">Address is required</div>
|
||||
<div *ngIf="address?.errors?.['pattern']">Must be a valid testnet4 address</div>
|
||||
<div *ngIf="address?.errors?.['forbiddenAddress']">You cannot use this address</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@if (!user) {
|
||||
<div class="alert alert-mempool d-block">
|
||||
To limit abuse, please <a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">log in</a> or <a routerLink="/signup" [queryParams]="{'redirectTo': '/testnet4/faucet'}">sign up</a> and link your Twitter account to use the faucet.
|
||||
</div>
|
||||
} @else if (!status?.access) {
|
||||
<div class="alert alert-mempool d-block">
|
||||
To use this feature, please <a routerLink="/services/account/settings">link your Twitter account</a>.
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<br>
|
||||
<div *ngIf="status?.address">
|
||||
If you no longer need your testnet4 coins, please consider sending them back to <a [routerLink]="['/address/' | relativeUrl, status.address]">{{ status.address }}</a> to replenish the faucet.
|
||||
</div>
|
||||
|
||||
<!-- Send back coins -->
|
||||
@if (status?.address) {
|
||||
<div class="mt-2 alert alert-info w-100">If you no longer need your testnet4 coins, please consider <a class="text-primary" [routerLink]="['/address/' | relativeUrl, status.address]"><u>sending them back</u></a> to replenish the faucet.</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -23,6 +23,9 @@
|
|||
.submit-button, .button-group, .button-group .btn {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.submit-button:disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#satoshis::after {
|
||||
content: 'sats';
|
||||
|
@ -41,4 +44,9 @@
|
|||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border-width: 1px;
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, ValidationErrors, Validators } from "@angular/forms";
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { ServicesApiServices } from '../../services/services-api.service';
|
||||
import { AudioService } from '../../services/audio.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Subscription, tap } from "rxjs";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { Component, OnDestroy, OnInit, ChangeDetectorRef } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators, ValidatorFn, AbstractControl, ValidationErrors } from "@angular/forms";
|
||||
import { Subscription } from "rxjs";
|
||||
import { StorageService } from "../../services/storage.service";
|
||||
import { ServicesApiServices } from "../../services/services-api.service";
|
||||
import { getRegex } from "../../shared/regex.utils";
|
||||
import { StateService } from "../../services/state.service";
|
||||
import { WebsocketService } from "../../services/websocket.service";
|
||||
import { AudioService } from "../../services/audio.service";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
|
||||
@Component({
|
||||
selector: 'app-faucet',
|
||||
|
@ -15,52 +15,91 @@ import { WebsocketService } from "../../services/websocket.service";
|
|||
styleUrls: ['./faucet.component.scss']
|
||||
})
|
||||
export class FaucetComponent implements OnInit, OnDestroy {
|
||||
user: any;
|
||||
loading: boolean = true;
|
||||
loading = true;
|
||||
error: string = '';
|
||||
user: any = undefined;
|
||||
txid: string = '';
|
||||
|
||||
faucetStatusSubscription: Subscription;
|
||||
status: {
|
||||
address?: string,
|
||||
access: boolean
|
||||
min: number,
|
||||
user_max: number,
|
||||
user_requests: number,
|
||||
min: number; // minimum amount to request at once (in sats)
|
||||
max: number; // maximum amount to request at once
|
||||
address?: string; // faucet address
|
||||
code: 'ok' | 'faucet_not_available' | 'faucet_maximum_reached' | 'faucet_too_soon';
|
||||
} | null = null;
|
||||
error = '';
|
||||
faucetForm: FormGroup;
|
||||
txid = '';
|
||||
|
||||
mempoolPositionSubscription: Subscription;
|
||||
confirmationSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private storageService: StorageService,
|
||||
private servicesApiService: ServicesApiServices,
|
||||
private websocketService: WebsocketService,
|
||||
private audioService: AudioService,
|
||||
private formBuilder: FormBuilder,
|
||||
private stateService: StateService,
|
||||
private websocketService: WebsocketService,
|
||||
private audioService: AudioService
|
||||
) {
|
||||
this.faucetForm = this.formBuilder.group({
|
||||
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4'))]],
|
||||
'satoshis': [0, [Validators.required, Validators.min(0), Validators.max(0)]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
ngOnDestroy() {
|
||||
this.stateService.markBlock$.next({});
|
||||
this.websocketService.stopTrackingTransaction();
|
||||
if (this.mempoolPositionSubscription) {
|
||||
this.mempoolPositionSubscription.unsubscribe();
|
||||
}
|
||||
if (this.confirmationSubscription) {
|
||||
this.confirmationSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.user = this.storageService.getAuth()?.user ?? null;
|
||||
this.initForm(5000, 500000);
|
||||
if (this.user) {
|
||||
try {
|
||||
this.servicesApiService.getFaucetStatus$().subscribe(status => {
|
||||
this.status = status;
|
||||
this.initForm(this.status.min, this.status.user_max);
|
||||
})
|
||||
} catch (e) {
|
||||
if (e?.status !== 403) {
|
||||
this.error = 'faucet_not_available';
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
} else {
|
||||
if (!this.user) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup form
|
||||
this.faucetStatusSubscription = this.servicesApiService.getFaucetStatus$().subscribe({
|
||||
next: (status) => {
|
||||
if (!status) {
|
||||
this.error = 'internal_server_error';
|
||||
return;
|
||||
}
|
||||
this.status = status;
|
||||
|
||||
const notFaucetAddressValidator = (faucetAddress: string): ValidatorFn => {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const forbidden = control.value === faucetAddress;
|
||||
return forbidden ? { forbiddenAddress: { value: control.value } } : null;
|
||||
};
|
||||
}
|
||||
this.faucetForm = this.formBuilder.group({
|
||||
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4')), notFaucetAddressValidator(this.status.address)]],
|
||||
'satoshis': [this.status.min, [Validators.required, Validators.min(this.status.min), Validators.max(this.status.max)]]
|
||||
});
|
||||
|
||||
if (this.status.code !== 'ok') {
|
||||
this.error = this.status.code;
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this.cd.markForCheck();
|
||||
},
|
||||
error: (response) => {
|
||||
this.loading = false;
|
||||
this.error = response.error;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
// Track transaction
|
||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||
this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
|
||||
if (txPosition && txPosition.txid === this.txid) {
|
||||
|
@ -78,18 +117,22 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
initForm(min: number, max: number): void {
|
||||
this.faucetForm = this.formBuilder.group({
|
||||
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4'))]],
|
||||
'satoshis': ['', [Validators.required, Validators.min(min), Validators.max(max)]]
|
||||
}, { validators: (formGroup): ValidationErrors | null => {
|
||||
if (this.status && !this.status?.user_requests) {
|
||||
return { customError: 'You have used the faucet too many times already! Come back later.'}
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
this.faucetForm.get('satoshis').setValue(min);
|
||||
this.loading = false;
|
||||
requestCoins(): void {
|
||||
this.error = null;
|
||||
this.txid = '';
|
||||
this.stateService.markBlock$.next({});
|
||||
this.servicesApiService.requestTestnet4Coins$(this.faucetForm.get('address')?.value, parseInt(this.faucetForm.get('satoshis')?.value))
|
||||
.subscribe({
|
||||
next: ((response) => {
|
||||
this.txid = response.txid;
|
||||
this.websocketService.startTrackTransaction(this.txid);
|
||||
this.audioService.playSound('cha-ching');
|
||||
this.cd.markForCheck();
|
||||
}),
|
||||
error: (response: HttpErrorResponse) => {
|
||||
this.error = response.error;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setAmount(value: number): void {
|
||||
|
@ -98,39 +141,13 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
requestCoins(): void {
|
||||
this.error = null;
|
||||
this.stateService.markBlock$.next({});
|
||||
this.servicesApiService.requestTestnet4Coins$(this.faucetForm.get('address')?.value, parseInt(this.faucetForm.get('satoshis')?.value))
|
||||
.subscribe({
|
||||
next: ((response) => {
|
||||
this.txid = response.txid;
|
||||
this.websocketService.startTrackTransaction(this.txid);
|
||||
this.audioService.playSound('cha-ching');
|
||||
}),
|
||||
error: (response: HttpErrorResponse) => {
|
||||
this.error = response.error;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.stateService.markBlock$.next({});
|
||||
this.websocketService.stopTrackingTransaction();
|
||||
if (this.mempoolPositionSubscription) {
|
||||
this.mempoolPositionSubscription.unsubscribe();
|
||||
}
|
||||
if (this.confirmationSubscription) {
|
||||
this.confirmationSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
get amount() { return this.faucetForm.get('satoshis')!; }
|
||||
get address() { return this.faucetForm.get('address')!; }
|
||||
get invalidAmount() {
|
||||
const amount = this.faucetForm.get('satoshis')!;
|
||||
return amount?.invalid && (amount.dirty || amount.touched)
|
||||
}
|
||||
|
||||
get address() { return this.faucetForm.get('address')!; }
|
||||
get invalidAddress() {
|
||||
const address = this.faucetForm.get('address')!;
|
||||
return address?.invalid && (address.dirty || address.touched)
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-faucet" *ngIf="stateService.env.OFFICIAL_MEMPOOL_SPACE && stateService.network === 'testnet4'">
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-faucet" *ngIf="stateService.isMempoolSpaceBuild && stateService.env.OFFICIAL_MEMPOOL_SPACE && stateService.network === 'testnet4'">
|
||||
<a class="nav-link" [routerLink]="['/faucet' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'faucet-drip']" [fixedWidth]="true" i18n-title="master-page.faucet" title="Faucet"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-docs">
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<a href="#" (click)="twitterLogin()"
|
||||
[class]="(disabled ? 'disabled': '') + (customClass ? customClass : 'w-100 btn mt-1 d-flex justify-content-center align-items-center')"
|
||||
style="background-color: #1DA1F2" [style]="width ? 'width: ' + width : ''">
|
||||
<img src="./resources/twitter.svg" height="25" style="padding: 2px" [alt]="buttonString + ' with Twitter'" />
|
||||
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
|
||||
</a>
|
|
@ -0,0 +1,25 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'app-twitter-login',
|
||||
templateUrl: './twitter-login.component.html',
|
||||
})
|
||||
export class TwitterLogin {
|
||||
@Input() width: string | null = null;
|
||||
@Input() customClass: string | null = null;
|
||||
@Input() buttonString: string= 'unset';
|
||||
@Input() redirectTo: string | null = null;
|
||||
@Output() clicked = new EventEmitter<boolean>();
|
||||
@Input() disabled: boolean = false;
|
||||
|
||||
constructor() {}
|
||||
|
||||
twitterLogin() {
|
||||
this.clicked.emit(true);
|
||||
if (this.redirectTo) {
|
||||
location.replace(`/api/v1/services/auth/login/twitter?redirectTo=${encodeURIComponent(this.redirectTo)}`);
|
||||
} else {
|
||||
location.replace(`/api/v1/services/auth/login/twitter?redirectTo=${location.href}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -105,19 +105,21 @@ if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
|||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerStatusComponent
|
||||
});
|
||||
routes[0].children.push({
|
||||
path: 'faucet',
|
||||
canActivate: [(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
|
||||
return state.url.startsWith('/testnet4/');
|
||||
}],
|
||||
component: StartComponent,
|
||||
data: { preload: true, networkSpecific: true },
|
||||
children: [{
|
||||
path: '',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: FaucetComponent,
|
||||
}]
|
||||
})
|
||||
if (window['isMempoolSpaceBuild']) {
|
||||
routes[0].children.push({
|
||||
path: 'faucet',
|
||||
canActivate: [(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
|
||||
return state.url.startsWith('/testnet4/');
|
||||
}],
|
||||
component: StartComponent,
|
||||
data: { preload: true, networkSpecific: true },
|
||||
children: [{
|
||||
path: '',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: FaucetComponent,
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -161,7 +161,7 @@ export class ServicesApiServices {
|
|||
}
|
||||
|
||||
getFaucetStatus$() {
|
||||
return this.httpClient.get<{ address?: string, access: boolean, min: number, user_max: number, user_requests: number }>(`${SERVICES_API_PREFIX}/testnet4/faucet/status`, { responseType: 'json' });
|
||||
return this.httpClient.get<{ address?: string, min: number, max: number, code: 'ok' | 'faucet_not_available' | 'faucet_maximum_reached' | 'faucet_too_soon'}>(`${SERVICES_API_PREFIX}/testnet4/faucet/status`, { responseType: 'json' });
|
||||
}
|
||||
|
||||
requestTestnet4Coins$(address: string, sats: number) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, Input, OnInit } from "@angular/core";
|
|||
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
|
||||
|
||||
const MempoolErrors = {
|
||||
'bad_request': `Your request was not valid. Please try again.`,
|
||||
'internal_server_error': `Something went wrong, please try again later`,
|
||||
'acceleration_duplicated': `This transaction has already been accelerated.`,
|
||||
'acceleration_outbid': `Your fee delta is too low.`,
|
||||
|
@ -22,6 +23,12 @@ const MempoolErrors = {
|
|||
'waitlisted': `You are currently on the wait list. You will get notified once you are granted access.`,
|
||||
'not_whitelisted_by_any_pool': `You are not whitelisted by any mining pool`,
|
||||
'unauthorized': `You are not authorized to do this`,
|
||||
'faucet_too_soon': `You cannot request any more coins right now. Try again later.`,
|
||||
'faucet_not_available': `The faucet is not available right now. Try again later.`,
|
||||
'faucet_maximum_reached': `You are not allowed to request more coins`,
|
||||
'faucet_address_not_allowed': `You cannot use this address`,
|
||||
'faucet_below_minimum': `Requested amount is too small`,
|
||||
'faucet_above_maximum': `Requested amount is too high`,
|
||||
} as { [error: string]: string };
|
||||
|
||||
export function isMempoolError(error: string) {
|
||||
|
|
|
@ -115,6 +115,7 @@ import { BitcoinsatoshisPipe } from '../shared/pipes/bitcoinsatoshis.pipe';
|
|||
import { HttpErrorComponent } from '../shared/components/http-error/http-error.component';
|
||||
import { TwitterWidgetComponent } from '../components/twitter-widget/twitter-widget.component';
|
||||
import { FaucetComponent } from '../components/faucet/faucet.component';
|
||||
import { TwitterLogin } from '../components/twitter-login/twitter-login.component';
|
||||
|
||||
import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-directives/weight-directives';
|
||||
|
||||
|
@ -230,6 +231,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||
HttpErrorComponent,
|
||||
TwitterWidgetComponent,
|
||||
FaucetComponent,
|
||||
TwitterLogin,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -359,6 +361,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||
PendingStatsComponent,
|
||||
HttpErrorComponent,
|
||||
TwitterWidgetComponent,
|
||||
TwitterLogin,
|
||||
|
||||
MempoolBlockOverviewComponent,
|
||||
ClockchainComponent,
|
||||
|
|
Loading…
Add table
Reference in a new issue