From 21e1c8f7c068b9578832948067e480e3155a5734 Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 18 Sep 2025 21:44:24 +0200 Subject: [PATCH] Refactor ReceiveDialog.vue for Lightning invoice creation - Updated the form to create Lightning invoices instead of LNURL addresses, changing the validation schema and input fields accordingly. - Introduced new state management for created invoices and adjusted the submission logic to handle invoice creation. - Enhanced the UI to display invoice details, including amount, memo, and QR code generation for the invoice. - Removed unused components and streamlined the dialog's functionality for a more focused user experience. These changes improve the functionality and user interface of the ReceiveDialog component, facilitating easier invoice management for Bitcoin payments. --- .../wallet/components/ReceiveDialog.vue | 497 +++++++----------- src/modules/wallet/services/WalletService.ts | 93 +++- src/modules/wallet/views/WalletPage.vue | 8 - 3 files changed, 280 insertions(+), 318 deletions(-) diff --git a/src/modules/wallet/components/ReceiveDialog.vue b/src/modules/wallet/components/ReceiveDialog.vue index 5302c81..75d5f0d 100644 --- a/src/modules/wallet/components/ReceiveDialog.vue +++ b/src/modules/wallet/components/ReceiveDialog.vue @@ -3,20 +3,15 @@ import { ref, computed } from 'vue' import { useForm } from 'vee-validate' import { toTypedSchema } from '@vee-validate/zod' import * as z from 'zod' -import { nip19 } from 'nostr-tools' import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' -import { Switch } from '@/components/ui/switch' import { Label } from '@/components/ui/label' -import { Separator } from '@/components/ui/separator' -import { Badge } from '@/components/ui/badge' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { QrCode, Copy, Check, RefreshCw, Trash2, Plus } from 'lucide-vue-next' +import { QrCode, Copy, Check, Loader2 } from 'lucide-vue-next' import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import type { PayLink } from '../services/WalletService' +import type { Invoice } from '../services/WalletService' interface Props { open: boolean @@ -36,22 +31,16 @@ const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as any // Form validation schema const formSchema = toTypedSchema(z.object({ - description: z.string().min(1, "Description is required").max(200, "Description too long"), - minAmount: z.number().min(1, "Minimum amount must be at least 1 sat"), - maxAmount: z.number().min(1, "Maximum amount must be at least 1 sat"), - username: z.string().optional(), - allowComments: z.boolean().default(false) + amount: z.number().min(1, "Amount must be at least 1 sat").max(10000000, "Amount too large"), + memo: z.string().max(640, "Description too long").optional() })) // Form setup const form = useForm({ validationSchema: formSchema, initialValues: { - description: '', - minAmount: 1, - maxAmount: 100000, - username: '', - allowComments: false + amount: undefined, + memo: '' } }) @@ -59,36 +48,32 @@ const { resetForm, meta } = form const isFormValid = computed(() => meta.value.valid) // State -const activeTab = ref('create') -const selectedPayLink = ref(null) +const createdInvoice = ref(null) const copiedField = ref(null) const qrCode = ref(null) const isLoadingQR = ref(false) // Computed -const existingPayLinks = computed(() => walletService?.payLinks?.value || []) -const isCreating = computed(() => walletService?.isCreatingPayLink?.value || false) +const isCreating = computed(() => walletService?.isCreatingInvoice?.value || false) const error = computed(() => walletService?.error?.value) // Methods const onSubmit = form.handleSubmit(async (formValues) => { try { - const payLink = await walletService.createReceiveAddress({ - description: formValues.description, - minAmount: formValues.minAmount, - maxAmount: formValues.maxAmount, - username: formValues.username || undefined, - allowComments: formValues.allowComments + const invoice = await walletService.createInvoice({ + amount: formValues.amount, + memo: formValues.memo || 'Payment request' + // Let server use default expiry }) - - if (payLink) { - selectedPayLink.value = payLink - activeTab.value = 'existing' - resetForm() - toastService?.success('Receive address created successfully!') + + if (invoice) { + createdInvoice.value = invoice + console.log(createdInvoice.value) + await generateQRCode(invoice.payment_request) + toastService?.success('Invoice created successfully!') } } catch (error) { - console.error('Failed to create receive address:', error) + console.error('Failed to create invoice:', error) } }) @@ -97,7 +82,7 @@ async function copyToClipboard(text: string, field: string) { await navigator.clipboard.writeText(text) copiedField.value = field toastService?.success('Copied to clipboard!') - + setTimeout(() => { copiedField.value = null }, 2000) @@ -107,29 +92,13 @@ async function copyToClipboard(text: string, field: string) { } } -function encodeLNURL(url: string): string { - try { - // Convert URL to bytes - const bytes = new TextEncoder().encode(url) - // Encode as bech32 with 'lnurl' prefix - const bech32 = nip19.encodeBytes('lnurl', bytes) - // Return with lightning: prefix in uppercase - return `lightning:${bech32.toUpperCase()}` - } catch (error) { - console.error('Failed to encode LNURL:', error) - return url // Fallback to original URL - } -} - -async function generateQRCode(data: string) { - if (!data) return +async function generateQRCode(paymentRequest: string) { + if (!paymentRequest) return isLoadingQR.value = true try { - // Encode LNURL with proper bech32 format and lightning: prefix - const encodedLNURL = encodeLNURL(data) // Use the existing PaymentService QR code generation - qrCode.value = await paymentService?.generateQRCode(encodedLNURL) + qrCode.value = await paymentService?.generateQRCode(paymentRequest) } catch (error) { console.error('Failed to generate QR code:', error) toastService?.error('Failed to generate QR code') @@ -138,41 +107,34 @@ async function generateQRCode(data: string) { } } -async function deletePayLink(link: PayLink) { - if (await walletService.deletePayLink(link.id)) { - toastService?.success('Receive address deleted') - if (selectedPayLink.value?.id === link.id) { - selectedPayLink.value = null - } - } -} - -async function selectPayLink(link: PayLink) { - selectedPayLink.value = link - // Generate QR code when selecting a pay link - if (link.lnurl) { - await generateQRCode(link.lnurl) - } -} - function closeDialog() { emit('update:open', false) resetForm() - selectedPayLink.value = null - activeTab.value = 'create' + createdInvoice.value = null + qrCode.value = null +} + +function createAnother() { + resetForm() + createdInvoice.value = null + qrCode.value = null } // Handle dialog open/close state changes function onOpenChange(open: boolean) { - if (open && walletService) { - walletService.refresh() - } else if (!open) { - // Dialog is being closed (including X button) - just emit the update - emit('update:open', false) - // Clean up state - resetForm() - selectedPayLink.value = null - activeTab.value = 'create' + if (!open) { + closeDialog() + } +} + +// Format expiry time for display +function formatExpiry(seconds: number): string { + if (seconds < 3600) { + return `${Math.round(seconds / 60)} minutes` + } else if (seconds < 86400) { + return `${Math.round(seconds / 3600)} hours` + } else { + return `${Math.round(seconds / 86400)} days` } } @@ -186,245 +148,162 @@ function onOpenChange(open: boolean) { Receive Bitcoin - Create LNURL addresses and Lightning addresses to receive Bitcoin payments. + Create a Lightning invoice to receive Bitcoin payments. - - - Create New - Existing Addresses - + +
+
+ + + Amount (sats) * + + + + Amount to request in satoshis + + + - - - - - Description * - -