\ No newline at end of file
diff --git a/frontend/src/app/components/faucet/faucet.component.scss b/frontend/src/app/components/faucet/faucet.component.scss
index 084168ca4..d611f5a23 100644
--- a/frontend/src/app/components/faucet/faucet.component.scss
+++ b/frontend/src/app/components/faucet/faucet.component.scss
@@ -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;
-}
\ No newline at end of file
+}
+
+.invalid {
+ border-width: 1px;
+ border-color: var(--red);
+}
diff --git a/frontend/src/app/components/faucet/faucet.component.ts b/frontend/src/app/components/faucet/faucet.component.ts
index 98d7a0c57..afe9e9241 100644
--- a/frontend/src/app/components/faucet/faucet.component.ts
+++ b/frontend/src/app/components/faucet/faucet.component.ts
@@ -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 } 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,75 @@ 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
} | 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() {
+ if (this.faucetStatusSubscription) {
+ this.faucetStatusSubscription.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;
+
+ this.faucetForm = this.formBuilder.group({
+ 'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4'))]],
+ 'satoshis': [this.status.min, [Validators.required, Validators.min(this.status.min), Validators.max(this.status.max)]]
+ });
+
+ 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,26 +101,6 @@ 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;
- }
-
- setAmount(value: number): void {
- if (this.faucetForm) {
- this.faucetForm.get('satoshis').setValue(value);
- }
- }
-
requestCoins(): void {
this.error = null;
this.stateService.markBlock$.next({});
@@ -114,23 +117,19 @@ export class FaucetComponent implements OnInit, OnDestroy {
});
}
- ngOnDestroy(): void {
- this.stateService.markBlock$.next({});
- this.websocketService.stopTrackingTransaction();
- if (this.mempoolPositionSubscription) {
- this.mempoolPositionSubscription.unsubscribe();
- }
- if (this.confirmationSubscription) {
- this.confirmationSubscription.unsubscribe();
+ setAmount(value: number): void {
+ if (this.faucetForm) {
+ this.faucetForm.get('satoshis').setValue(value);
}
}
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)
diff --git a/frontend/src/app/components/twitter-login/twitter-login.component.html b/frontend/src/app/components/twitter-login/twitter-login.component.html
new file mode 100644
index 000000000..2fe2392e0
--- /dev/null
+++ b/frontend/src/app/components/twitter-login/twitter-login.component.html
@@ -0,0 +1,6 @@
+
+
+ {{ buttonString }}
+
diff --git a/frontend/src/app/components/twitter-login/twitter-login.component.ts b/frontend/src/app/components/twitter-login/twitter-login.component.ts
new file mode 100644
index 000000000..17583b00e
--- /dev/null
+++ b/frontend/src/app/components/twitter-login/twitter-login.component.ts
@@ -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
();
+ @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;
+ }
+}
diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts
index aec4be089..f57aa8524 100644
--- a/frontend/src/app/services/services-api.service.ts
+++ b/frontend/src/app/services/services-api.service.ts
@@ -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 }>(`${SERVICES_API_PREFIX}/testnet4/faucet/status`, { responseType: 'json' });
}
requestTestnet4Coins$(address: string, sats: number) {
diff --git a/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts b/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts
index 07b96427d..4bb86f16d 100644
--- a/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts
+++ b/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts
@@ -22,6 +22,9 @@ 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`,
} as { [error: string]: string };
export function isMempoolError(error: string) {
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 89d62b375..777ca7180 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -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,