mirror of
https://github.com/block-core/angor-hub-old.git
synced 2026-02-10 20:44:21 +01:00
Refactor ZapComponent and ProfileComponent: update snackbar duration, modify canUseZap method, and adjust ZapComponent's form validation, error handling, and LNURLPayRequest processing.
This commit is contained in:
@@ -438,7 +438,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
openSnackBar(message: string, action: string = 'dismiss'): void {
|
||||
this._snackBar.open(message, action, { duration: 1300 });
|
||||
this._snackBar.open(message, action, { duration: 3000 });
|
||||
}
|
||||
|
||||
async canUseZap(): Promise<boolean> {
|
||||
@@ -446,14 +446,14 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||
if (canReceiveZap) {
|
||||
return true;
|
||||
} else {
|
||||
this.openSnackBar("User can't receive zaps");
|
||||
this.openSnackBar("Using Zap is not possible. Please complete your profile to include lud06 or lud16.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
openZapDialog(eventId: string = ""): void {
|
||||
if (this.canUseZap()) {
|
||||
async openZapDialog(eventId: string = ""): Promise<void> {
|
||||
const canZap = await this.canUseZap();
|
||||
if (canZap) {
|
||||
const zapData: ZapDialogData = {
|
||||
lud16: this.profileUser.lud16,
|
||||
lud06: this.profileUser.lud06,
|
||||
@@ -470,8 +470,6 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
toggleLike() {
|
||||
this.isLiked = !this.isLiked;
|
||||
|
||||
@@ -518,6 +516,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||
this._eventService
|
||||
.sendTextEvent(this.eventInput.nativeElement.value)
|
||||
.then(() => {
|
||||
this.eventInput.nativeElement.value = "";
|
||||
this._changeDetectorRef.markForCheck();
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
import { LNURLPayRequest, LNURLInvoice } from 'app/services/interfaces';
|
||||
import { SignerService } from 'app/services/signer.service';
|
||||
import { RelayService } from 'app/services/relay.service';
|
||||
import { finalizeEvent, NostrEvent, UnsignedEvent } from 'nostr-tools';
|
||||
import { AngorCardComponent } from "../../../@angor/components/card/card.component";
|
||||
import { Utilities } from 'app/services/utilities';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
ValidationErrors,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { hexToBytes } from '@noble/hashes/utils';
|
||||
import { LNURLInvoice, LNURLPayRequest } from 'app/services/interfaces';
|
||||
import { RelayService } from 'app/services/relay.service';
|
||||
import { SignerService } from 'app/services/signer.service';
|
||||
import { Utilities } from 'app/services/utilities';
|
||||
import { finalizeEvent, NostrEvent, UnsignedEvent } from 'nostr-tools';
|
||||
import { AngorCardComponent } from '../../../@angor/components/card/card.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-zap',
|
||||
@@ -29,22 +36,20 @@ import { hexToBytes } from '@noble/hashes/utils';
|
||||
MatSelectModule,
|
||||
TextFieldModule,
|
||||
ReactiveFormsModule,
|
||||
AngorCardComponent
|
||||
AngorCardComponent,
|
||||
],
|
||||
})
|
||||
export class ZapComponent implements OnInit {
|
||||
private readonly formBuilder = inject(FormBuilder);
|
||||
private readonly signerService = inject(SignerService);
|
||||
private readonly relayService = inject(RelayService);
|
||||
private readonly utilities = inject(Utilities);
|
||||
|
||||
|
||||
constructor(private util: Utilities,
|
||||
) {
|
||||
|
||||
}
|
||||
sendZapForm!: FormGroup;
|
||||
payRequest: LNURLPayRequest | null = null;
|
||||
invoice: LNURLInvoice = { pr: '' };
|
||||
invoice: LNURLInvoice = {
|
||||
pr: '',
|
||||
};
|
||||
canZap = false;
|
||||
loading = false;
|
||||
error = '';
|
||||
@@ -55,50 +60,79 @@ export class ZapComponent implements OnInit {
|
||||
|
||||
private initializeForm(): void {
|
||||
this.sendZapForm = this.formBuilder.group({
|
||||
lightningAddress: ['', [Validators.required, this.validateLightningAddress]],
|
||||
lightningAddress: [
|
||||
'',
|
||||
[Validators.required, this.validateLightningAddress],
|
||||
],
|
||||
eventId: [''], // Optional for zapping specific events
|
||||
amount: ['', [Validators.required, Validators.min(1)]],
|
||||
comment: [''],
|
||||
});
|
||||
}
|
||||
|
||||
private validateLightningAddress(control: AbstractControl): ValidationErrors | null {
|
||||
private validateLightningAddress(
|
||||
control: AbstractControl
|
||||
): ValidationErrors | null {
|
||||
const value = control.value;
|
||||
return value.includes('@') ? null : { invalidFormat: true };
|
||||
return value.includes('@')
|
||||
? null
|
||||
: {
|
||||
invalidFormat: true,
|
||||
};
|
||||
}
|
||||
|
||||
private getCallbackUrl(lightningAddress: string): string | null {
|
||||
if (lightningAddress.includes('@')) {
|
||||
const [username, domain] = lightningAddress.split('@');
|
||||
return `https://${domain}/.well-known/lnurlp/${username}`;
|
||||
} else if (lightningAddress.toLowerCase().startsWith('lnurl')) {
|
||||
return this.util.convertBech32ToText(lightningAddress).toString();
|
||||
try {
|
||||
if (lightningAddress.includes('@')) {
|
||||
const [username, domain] = lightningAddress.split('@');
|
||||
return `https://${domain}/.well-known/lnurlp/${username}`;
|
||||
} else if (lightningAddress.toLowerCase().startsWith('lnurl')) {
|
||||
return this.utilities
|
||||
.convertBech32ToText(lightningAddress)
|
||||
.toString();
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error generating callback URL:', error);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async fetchPayRequest(): Promise<void> {
|
||||
this.resetState();
|
||||
const lightningAddress = this.sendZapForm.get('lightningAddress')?.value;
|
||||
|
||||
const lightningAddress =
|
||||
this.sendZapForm.get('lightningAddress')?.value;
|
||||
if (!lightningAddress) {
|
||||
this.setError('Lightning Address is required.');
|
||||
return;
|
||||
}
|
||||
const callbackUrl = this.getCallbackUrl(lightningAddress);
|
||||
|
||||
const callbackUrl = this.getCallbackUrl(lightningAddress);
|
||||
if (!callbackUrl) {
|
||||
this.setError('Invalid Lightning Address.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(callbackUrl);
|
||||
if (!response.ok) throw new Error('Failed to fetch pay request.');
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch pay request: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'ERROR') {
|
||||
this.setError(result.reason || 'Error fetching the pay request.');
|
||||
return;
|
||||
throw new Error(
|
||||
result.reason || 'Error fetching the pay request.'
|
||||
);
|
||||
}
|
||||
|
||||
this.payRequest = result as LNURLPayRequest;
|
||||
this.canZap = true;
|
||||
this.configureAmountValidators();
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
this.setError(error.message || 'Error connecting to the server.');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -127,97 +161,103 @@ export class ZapComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.resetState();
|
||||
const { lightningAddress, eventId, amount, comment } = this.sendZapForm.value;
|
||||
const { lightningAddress, eventId, amount, comment } =
|
||||
this.sendZapForm.value;
|
||||
|
||||
if (!this.payRequest) {
|
||||
this.setError('Pay request is not loaded.');
|
||||
return;
|
||||
}
|
||||
|
||||
const callback = new URL(this.payRequest.callback);
|
||||
const query = new URLSearchParams({
|
||||
amount: (amount * 1000).toString(),
|
||||
});
|
||||
|
||||
if (comment && this.payRequest.commentAllowed) {
|
||||
query.set('comment', comment);
|
||||
}
|
||||
|
||||
if (eventId) {
|
||||
const zapRequest = await this.createAndSignZapRequest(eventId, comment);
|
||||
query.set('nostr', JSON.stringify(zapRequest));
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${callback.origin}${callback.pathname}?${query.toString()}`);
|
||||
if (!response.ok) throw new Error('Failed to fetch invoice.');
|
||||
const callback = new URL(this.payRequest.callback);
|
||||
const query = new URLSearchParams({
|
||||
amount: (amount * 1000).toString(),
|
||||
});
|
||||
|
||||
if (comment && this.payRequest.commentAllowed) {
|
||||
query.set('comment', comment);
|
||||
}
|
||||
|
||||
if (eventId) {
|
||||
const zapRequest = await this.createAndSignZapRequest(
|
||||
eventId,
|
||||
comment
|
||||
);
|
||||
query.set('nostr', JSON.stringify(zapRequest));
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${callback.origin}${callback.pathname}?${query.toString()}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch invoice: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'ERROR') {
|
||||
this.setError(result.reason || 'Error fetching the invoice.');
|
||||
return;
|
||||
throw new Error(result.reason || 'Error fetching the invoice.');
|
||||
}
|
||||
|
||||
this.invoice = result;
|
||||
} catch (error) {
|
||||
this.setError(error.message || 'Error fetching the invoice.');
|
||||
} catch (error: any) {
|
||||
this.setError(error.message || 'Error processing the zap request.');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async createAndSignZapRequest(eventId: string, msg?: string): Promise<NostrEvent> {
|
||||
private async createAndSignZapRequest(
|
||||
eventId: string,
|
||||
msg?: string
|
||||
): Promise<NostrEvent> {
|
||||
try {
|
||||
const unsignedZapRequest = this.createZapRequestData(eventId, msg);
|
||||
|
||||
let signedEvent: NostrEvent;
|
||||
if (this.signerService.isUsingSecretKey()) {
|
||||
const privateKey = await this.signerService.getDecryptedSecretKey();
|
||||
if (!privateKey) throw new Error('Private key could not be retrieved.');
|
||||
const signedEvent = this.signerService.isUsingSecretKey()
|
||||
? finalizeEvent(
|
||||
unsignedZapRequest,
|
||||
hexToBytes(
|
||||
await this.signerService.getDecryptedSecretKey()
|
||||
)
|
||||
)
|
||||
: await this.signerService.signEventWithExtension(
|
||||
unsignedZapRequest
|
||||
);
|
||||
|
||||
const privateKeyBytes = hexToBytes(privateKey);
|
||||
signedEvent = finalizeEvent(unsignedZapRequest, privateKeyBytes);
|
||||
} else {
|
||||
signedEvent = await this.signerService.signEventWithExtension(unsignedZapRequest);
|
||||
if (!signedEvent) {
|
||||
throw new Error('Signing failed. Signed event is null.');
|
||||
}
|
||||
|
||||
if (!signedEvent) throw new Error('Signing failed. Signed event is null.');
|
||||
return signedEvent;
|
||||
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Error creating and signing zap request:', error);
|
||||
throw new Error('Failed to create and sign zap request.');
|
||||
}
|
||||
}
|
||||
|
||||
private createZapRequestData(eventId: string, msg?: string): UnsignedEvent {
|
||||
const tags = [
|
||||
['e', eventId],
|
||||
['p', this.payRequest?.nostrPubkey || ''],
|
||||
['relays', ...this.relayService.getConnectedRelays()],
|
||||
];
|
||||
|
||||
return {
|
||||
kind: 9734,
|
||||
content: msg || '',
|
||||
tags: tags,
|
||||
tags: [
|
||||
['e', eventId],
|
||||
['p', this.payRequest?.nostrPubkey || ''],
|
||||
['relays', ...this.relayService.getConnectedRelays()],
|
||||
],
|
||||
pubkey: this.signerService.getPublicKey(),
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
}
|
||||
|
||||
async sendZapToRelay(signedEvent: NostrEvent): Promise<void> {
|
||||
try {
|
||||
await this.relayService.publishEventToWriteRelays(signedEvent);
|
||||
console.log('Zap event sent successfully');
|
||||
} catch (error) {
|
||||
this.setError('Failed to send zap event to relays');
|
||||
console.error('Error sending zap event:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private resetState(): void {
|
||||
this.error = '';
|
||||
this.loading = true;
|
||||
this.invoice = { pr: '' };
|
||||
this.invoice = {
|
||||
pr: '',
|
||||
};
|
||||
}
|
||||
|
||||
private setError(message: string): void {
|
||||
@@ -225,5 +265,3 @@ export class ZapComponent implements OnInit {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user