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:
parent
895887c465
commit
cdd00bf747
1 changed files with 104 additions and 2 deletions
|
|
@ -12,6 +12,7 @@ import CurrencyDisplay from '@/components/ui/CurrencyDisplay.vue'
|
||||||
import ReceiveDialog from '../components/ReceiveDialog.vue'
|
import ReceiveDialog from '../components/ReceiveDialog.vue'
|
||||||
import SendDialog from '../components/SendDialog.vue'
|
import SendDialog from '../components/SendDialog.vue'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
|
import { nip19 } from 'nostr-tools'
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
const walletService = injectService(SERVICE_TOKENS.WALLET_SERVICE) as any
|
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 showReceiveDialog = ref(false)
|
||||||
const showSendDialog = ref(false)
|
const showSendDialog = ref(false)
|
||||||
const selectedTab = ref('transactions')
|
const selectedTab = ref('transactions')
|
||||||
|
const defaultQrCode = ref<string | null>(null)
|
||||||
|
const isGeneratingQR = ref(false)
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const transactions = computed(() => walletService?.transactions?.value || [])
|
const transactions = computed(() => walletService?.transactions?.value || [])
|
||||||
|
|
@ -35,6 +38,9 @@ const totalBalance = computed(() => {
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const payLinks = computed(() => walletService?.payLinks?.value || [])
|
||||||
|
const firstPayLink = computed(() => payLinks.value[0] || null)
|
||||||
|
|
||||||
// Get transactions grouped by date
|
// Get transactions grouped by date
|
||||||
const groupedTransactions = computed(() => {
|
const groupedTransactions = computed(() => {
|
||||||
const groups: Record<string, typeof transactions.value> = {}
|
const groups: Record<string, typeof transactions.value> = {}
|
||||||
|
|
@ -60,6 +66,10 @@ async function refresh() {
|
||||||
await walletService?.refresh()
|
await walletService?.refresh()
|
||||||
// Also refresh auth data to update balance
|
// Also refresh auth data to update balance
|
||||||
await authService?.refresh()
|
await authService?.refresh()
|
||||||
|
// Regenerate QR if pay link is available
|
||||||
|
if (firstPayLink.value) {
|
||||||
|
await generateDefaultQR()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTransactionIcon(type: string, status: string) {
|
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
|
// Initialize on mount
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
refresh()
|
await refresh()
|
||||||
|
// Generate QR for first pay link if available
|
||||||
|
if (firstPayLink.value) {
|
||||||
|
await generateDefaultQR()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -143,6 +188,63 @@ onMounted(() => {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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 -->
|
||||||
<Tabs v-model="selectedTab" class="w-full">
|
<Tabs v-model="selectedTab" class="w-full">
|
||||||
<TabsList class="grid w-full grid-cols-2">
|
<TabsList class="grid w-full grid-cols-2">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue