Merge pull request #5091 from mempool/mononaut/faucet-ui-changes
Requested changes to testnet4 faucet UI
This commit is contained in:
		
						commit
						2341b1d79e
					
				| @ -13,16 +13,16 @@ | |||||||
|         <a class="text-primary" [href]="'/testnet4/tx/' + txid">{{ txid }}</a> |         <a class="text-primary" [href]="'/testnet4/tx/' + txid">{{ txid }}</a> | ||||||
|       </div> |       </div> | ||||||
|     } |     } | ||||||
|     @else if (loading) { |     @if (loading) { | ||||||
|       <p>Loading faucet...</p> |       <p>Loading faucet...</p> | ||||||
|       <div class="spinner-border text-light"></div> |       <div class="spinner-border text-light"></div> | ||||||
|     } @else if (!user) { |     } @else if (!user) { | ||||||
|       <!-- User not logged in --> |       <!-- User not logged in --> | ||||||
|       <div class="alert alert-mempool d-block text-center w-100"> |       <div class="alert alert-mempool d-block text-center w-100"> | ||||||
|         <div class="d-inline align-middle"> |         <div class="d-inline align-middle"> | ||||||
|           <span>To limit abuse, </span> |           <span>To use the faucet, please </span> | ||||||
|           <a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">authenticate </a> |           <a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">login</a> | ||||||
|           <span class="mr-2">or</span> |           <span class="mr-2"> or</span> | ||||||
|         </div> |         </div> | ||||||
|         <app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login> |         <app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login> | ||||||
|       </div> |       </div> | ||||||
| @ -31,19 +31,18 @@ | |||||||
|       <!-- User logged in but not a paid user or did not link its Twitter account --> |       <!-- 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="alert alert-mempool d-block text-center w-100"> | ||||||
|         <div class="d-inline align-middle"> |         <div class="d-inline align-middle"> | ||||||
|           <span class="mb-2 mr-2">To limit abuse</span> |           <span class="mb-2 mr-2">To use the faucet, please</span> | ||||||
|         </div> |         </div> | ||||||
|         <app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login> |         <app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login> | ||||||
|       </div> |       </div> | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     @else if (error) { |     @else if (error) { | ||||||
|       <!-- User can request --> |       <!-- User can request --> | ||||||
|       <app-mempool-error class="w-100" [error]="error"></app-mempool-error> |       <app-mempool-error class="w-100" [error]="error"></app-mempool-error> | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @if (!loading) { |     @if (!loading) { | ||||||
|       <form [formGroup]="faucetForm" class="formGroup" (submit)="requestCoins()" [style]="(error || !this.user) ? 'opacity: 0.3; pointer-events: none' : ''"> |       <form [formGroup]="faucetForm" class="formGroup" (submit)="requestCoins()"> | ||||||
|         <div class="row"> |         <div class="row"> | ||||||
|           <div class="col"> |           <div class="col"> | ||||||
|             <div class="form-group mb-0"> |             <div class="form-group mb-0"> | ||||||
| @ -68,7 +67,7 @@ | |||||||
|                   <span class="input-group-text" i18n="address">Address</span> |                   <span class="input-group-text" i18n="address">Address</span> | ||||||
|                 </div> |                 </div> | ||||||
|                 <input type="text" class="form-control" [class]="{invalid: invalidAddress}" formControlName="address" id="address" placeholder="tb1q..."> |                 <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> |                 <button type="submit" class="btn btn-primary submit-button" [disabled]="!faucetForm.valid || !faucetForm.get('address')?.dirty || isDisabled()" i18n="testnet4.request-coins">Request Testnet4 Coins</button> | ||||||
|               </div> |               </div> | ||||||
|               <div class="text-danger text-left" *ngIf="invalidAddress"> |               <div class="text-danger text-left" *ngIf="invalidAddress"> | ||||||
|                 <div *ngIf="address?.errors?.['required']">Address is required</div> |                 <div *ngIf="address?.errors?.['required']">Address is required</div> | ||||||
| @ -83,7 +82,7 @@ | |||||||
| 
 | 
 | ||||||
|     <!-- Send back coins --> |     <!-- Send back coins --> | ||||||
|     @if (status?.address) {   |     @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 class="mt-4 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> | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ | |||||||
|     flex-grow: 1; |     flex-grow: 1; | ||||||
|   } |   } | ||||||
|   .submit-button:disabled { |   .submit-button:disabled { | ||||||
|     pointer-events: none; |     cursor: not-allowed; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   #satoshis::after { |   #satoshis::after { | ||||||
|  | |||||||
| @ -41,10 +41,7 @@ export class FaucetComponent implements OnInit, OnDestroy { | |||||||
|     private websocketService: WebsocketService, |     private websocketService: WebsocketService, | ||||||
|     private audioService: AudioService |     private audioService: AudioService | ||||||
|   ) { |   ) { | ||||||
|     this.faucetForm = this.formBuilder.group({ |     this.initForm(5000, 500_000, null); | ||||||
|       'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4'))]], |  | ||||||
|       'satoshis': [0, [Validators.required, Validators.min(0), Validators.max(0)]] |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnDestroy() { |   ngOnDestroy() { | ||||||
| @ -66,38 +63,7 @@ export class FaucetComponent implements OnInit, OnDestroy { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Setup form
 |     // Setup form
 | ||||||
|     this.faucetStatusSubscription = this.servicesApiService.getFaucetStatus$().subscribe({ |     this.updateFaucetStatus(); | ||||||
|       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
 |     // Track transaction
 | ||||||
|     this.websocketService.want(['blocks', 'mempool-blocks']); |     this.websocketService.want(['blocks', 'mempool-blocks']); | ||||||
| @ -117,7 +83,34 @@ export class FaucetComponent implements OnInit, OnDestroy { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   updateFaucetStatus(): void { | ||||||
|  |     this.servicesApiService.getFaucetStatus$().subscribe({ | ||||||
|  |       next: (status) => { | ||||||
|  |         if (!status) { | ||||||
|  |           this.error = 'internal_server_error'; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         this.status = status; | ||||||
|  |         if (this.status.code !== 'ok') { | ||||||
|  |           this.error = this.status.code; | ||||||
|  |           this.updateForm(this.status.min ?? 5000, this.status.max ?? 500_000, this.status.address); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         // update the form with the proper validation parameters
 | ||||||
|  |         this.updateForm(this.status.min, this.status.max, this.status.address); | ||||||
|  |       }, | ||||||
|  |       error: (response) => { | ||||||
|  |         this.loading = false; | ||||||
|  |         this.error = response.error; | ||||||
|  |         this.cd.markForCheck(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   requestCoins(): void { |   requestCoins(): void { | ||||||
|  |     if (this.isDisabled()) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     this.error = null; |     this.error = null; | ||||||
|     this.txid = ''; |     this.txid = ''; | ||||||
|     this.stateService.markBlock$.next({}); |     this.stateService.markBlock$.next({}); | ||||||
| @ -127,6 +120,7 @@ export class FaucetComponent implements OnInit, OnDestroy { | |||||||
|         this.txid = response.txid; |         this.txid = response.txid; | ||||||
|         this.websocketService.startTrackTransaction(this.txid); |         this.websocketService.startTrackTransaction(this.txid); | ||||||
|         this.audioService.playSound('cha-ching'); |         this.audioService.playSound('cha-ching'); | ||||||
|  |         this.updateFaucetStatus(); | ||||||
|         this.cd.markForCheck(); |         this.cd.markForCheck(); | ||||||
|       }), |       }), | ||||||
|       error: (response: HttpErrorResponse) => { |       error: (response: HttpErrorResponse) => { | ||||||
| @ -135,9 +129,44 @@ export class FaucetComponent implements OnInit, OnDestroy { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   isDisabled(): boolean { | ||||||
|  |     return !(this.user && this.status?.code === 'ok' && !this.error); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getNotFaucetAddressValidator(faucetAddress: string): ValidatorFn { | ||||||
|  |     return faucetAddress ? (control: AbstractControl): ValidationErrors | null => { | ||||||
|  |       const forbidden = control.value === faucetAddress; | ||||||
|  |       return forbidden ? { forbiddenAddress: { value: control.value } } : null; | ||||||
|  |     }: () => null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   initForm(min: number, max: number, faucetAddress: string): void { | ||||||
|  |     this.faucetForm = this.formBuilder.group({ | ||||||
|  |       'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4')), this.getNotFaucetAddressValidator(faucetAddress)]], | ||||||
|  |       'satoshis': [min, [Validators.required, Validators.min(min), Validators.max(max)]] | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     this.loading = false; | ||||||
|  |     this.cd.markForCheck(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateForm(min, max, faucetAddress: string): void { | ||||||
|  |     if (!this.faucetForm) { | ||||||
|  |       this.initForm(min, max, faucetAddress); | ||||||
|  |     } else { | ||||||
|  |       this.faucetForm.get('address').setValidators([Validators.required, Validators.pattern(getRegex('address', 'testnet4')), this.getNotFaucetAddressValidator(faucetAddress)]); | ||||||
|  |       this.faucetForm.get('satoshis').setValidators([Validators.required, Validators.min(min), Validators.max(max)]); | ||||||
|  |       this.faucetForm.get('satoshis').setValue(Math.max(min, this.faucetForm.get('satoshis').value)); | ||||||
|  |       this.faucetForm.get('satoshis').updateValueAndValidity(); | ||||||
|  |       this.faucetForm.get('satoshis').markAsDirty(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setAmount(value: number): void { |   setAmount(value: number): void { | ||||||
|     if (this.faucetForm) { |     if (this.faucetForm) { | ||||||
|       this.faucetForm.get('satoshis').setValue(value); |       this.faucetForm.get('satoshis').setValue(value); | ||||||
|  |       this.faucetForm.get('satoshis').updateValueAndValidity(); | ||||||
|  |       this.faucetForm.get('satoshis').markAsDirty(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user