Implement QR code generation for LNURL payment links in WalletPage

- Added functionality to generate a QR code for the first available pay link using LNURL encoding.
- Introduced reactive properties to manage QR code state and generation status.
- Updated the onMounted lifecycle hook to generate the QR code upon component mount if a pay link is available.
- Enhanced the UI to display the generated QR code along with payment range and lightning address information.

These changes improve the user experience by facilitating quick payment link sharing through QR codes.
This commit is contained in:
padreug 2025-09-15 00:52:52 +02:00
parent 895887c465
commit cdd00bf747

View file

@ -12,6 +12,7 @@ import CurrencyDisplay from '@/components/ui/CurrencyDisplay.vue'
import ReceiveDialog from '../components/ReceiveDialog.vue'
import SendDialog from '../components/SendDialog.vue'
import { format } from 'date-fns'
import { nip19 } from 'nostr-tools'
// Services
const walletService = injectService(SERVICE_TOKENS.WALLET_SERVICE) as any
@ -22,6 +23,8 @@ const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
const showReceiveDialog = ref(false)
const showSendDialog = ref(false)
const selectedTab = ref('transactions')
const defaultQrCode = ref<string | null>(null)
const isGeneratingQR = ref(false)
// Computed
const transactions = computed(() => walletService?.transactions?.value || [])
@ -35,6 +38,9 @@ const totalBalance = computed(() => {
}, 0)
})
const payLinks = computed(() => walletService?.payLinks?.value || [])
const firstPayLink = computed(() => payLinks.value[0] || null)
// Get transactions grouped by date
const groupedTransactions = computed(() => {
const groups: Record<string, typeof transactions.value> = {}
@ -60,6 +66,10 @@ async function refresh() {
await walletService?.refresh()
// Also refresh auth data to update balance
await authService?.refresh()
// Regenerate QR if pay link is available
if (firstPayLink.value) {
await generateDefaultQR()
}
}
function getTransactionIcon(type: string, status: string) {
@ -77,9 +87,44 @@ function getTransactionColor(type: string, status: string) {
}
// QR Code generation
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 generateDefaultQR() {
if (!firstPayLink.value?.lnurl) return
isGeneratingQR.value = true
try {
// Encode LNURL with proper bech32 format and lightning: prefix
const encodedLNURL = encodeLNURL(firstPayLink.value.lnurl)
// Use the existing PaymentService QR code generation
defaultQrCode.value = await paymentService?.generateQRCode(encodedLNURL)
} catch (error) {
console.error('Failed to generate default QR code:', error)
} finally {
isGeneratingQR.value = false
}
}
// Initialize on mount
onMounted(() => {
refresh()
onMounted(async () => {
await refresh()
// Generate QR for first pay link if available
if (firstPayLink.value) {
await generateDefaultQR()
}
})
</script>
@ -143,6 +188,63 @@ onMounted(() => {
</CardContent>
</Card>
<!-- Quick Receive QR Code -->
<Card v-if="firstPayLink" class="mb-6">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<QrCode class="h-5 w-5" />
Quick Receive
</CardTitle>
<CardDescription>{{ firstPayLink.description }}</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col sm:flex-row items-center gap-6">
<!-- QR Code -->
<div class="flex-shrink-0">
<div v-if="isGeneratingQR" class="w-48 h-48 flex items-center justify-center bg-muted rounded-lg">
<RefreshCw class="h-8 w-8 animate-spin text-muted-foreground" />
</div>
<div v-else-if="defaultQrCode" class="bg-white p-4 rounded-lg">
<img
:src="defaultQrCode"
alt="LNURL QR Code"
class="w-48 h-48"
/>
</div>
<div v-else class="w-48 h-48 flex items-center justify-center bg-muted rounded-lg">
<QrCode class="h-12 w-12 text-muted-foreground opacity-50" />
</div>
</div>
<!-- Address Info -->
<div class="flex-1 space-y-4 w-full">
<div>
<h4 class="font-medium mb-2">Payment Range</h4>
<div class="text-2xl font-bold text-green-600">
{{ firstPayLink.min?.toLocaleString() }} - {{ firstPayLink.max?.toLocaleString() }} sats
</div>
</div>
<div v-if="firstPayLink.lnaddress">
<h4 class="font-medium mb-2">Lightning Address</h4>
<div class="font-mono text-sm bg-muted px-3 py-2 rounded">
{{ firstPayLink.lnaddress }}
</div>
</div>
<Button
variant="outline"
@click="showReceiveDialog = true"
class="w-full sm:w-auto"
>
<QrCode class="h-4 w-4 mr-2" />
Manage Addresses
</Button>
</div>
</div>
</CardContent>
</Card>
<!-- Tabs -->
<Tabs v-model="selectedTab" class="w-full">
<TabsList class="grid w-full grid-cols-2">