Enhance SendDialog and WalletPage with QR code scanning integration

- Added initialDestination prop to SendDialog for pre-filling the destination field.
- Implemented a watcher to update the destination field when initialDestination changes.
- Integrated QRScanner component into WalletPage, allowing users to scan QR codes for payment destinations.
- Updated SendDialog to accept scanned destination and improved user feedback with toast notifications.

These changes streamline the payment process by enabling QR code scanning directly within the wallet interface.
This commit is contained in:
padreug 2025-09-18 22:34:29 +02:00
parent 58b785724e
commit a5f800ef74
2 changed files with 70 additions and 6 deletions

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref, watch } from 'vue'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
@ -14,6 +14,7 @@ import QRScanner from '@/components/ui/qr-scanner.vue'
interface Props { interface Props {
open: boolean open: boolean
initialDestination?: string
} }
interface Emits { interface Emits {
@ -38,7 +39,7 @@ const formSchema = toTypedSchema(z.object({
const form = useForm({ const form = useForm({
validationSchema: formSchema, validationSchema: formSchema,
initialValues: { initialValues: {
destination: '', destination: props.initialDestination || '',
amount: 100, amount: 100,
comment: '' comment: ''
} }
@ -47,6 +48,13 @@ const form = useForm({
const { resetForm, values, meta, setFieldValue } = form const { resetForm, values, meta, setFieldValue } = form
const isFormValid = computed(() => meta.value.valid) const isFormValid = computed(() => meta.value.valid)
// Watch for prop changes
watch(() => props.initialDestination, (newDestination) => {
if (newDestination) {
setFieldValue('destination', newDestination)
}
}, { immediate: true })
// State // State
const isSending = computed(() => walletService?.isSendingPayment?.value || false) const isSending = computed(() => walletService?.isSendingPayment?.value || false)
const showScanner = ref(false) const showScanner = ref(false)

View file

@ -6,9 +6,11 @@ import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
import { RefreshCw, Send, QrCode, ArrowUpRight, ArrowDownLeft, Clock, Wallet, Copy, Check } from 'lucide-vue-next' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { RefreshCw, Send, QrCode, ArrowUpRight, ArrowDownLeft, Clock, Wallet, ScanLine, Copy, Check } from 'lucide-vue-next'
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 QRScanner from '@/components/ui/qr-scanner.vue'
import { format } from 'date-fns' import { format } from 'date-fns'
import { nip19 } from 'nostr-tools' import { nip19 } from 'nostr-tools'
@ -21,6 +23,8 @@ const toastService = injectService(SERVICE_TOKENS.TOAST_SERVICE) as any
// State // State
const showReceiveDialog = ref(false) const showReceiveDialog = ref(false)
const showSendDialog = ref(false) const showSendDialog = ref(false)
const showQRScanner = ref(false)
const scannedDestination = ref<string>('')
const selectedTab = ref('transactions') const selectedTab = ref('transactions')
const defaultQrCode = ref<string | null>(null) const defaultQrCode = ref<string | null>(null)
const isGeneratingQR = ref(false) const isGeneratingQR = ref(false)
@ -144,6 +148,26 @@ function handleQRClick() {
const encodedLNURL = encodeLNURL(firstPayLink.value.lnurl) const encodedLNURL = encodeLNURL(firstPayLink.value.lnurl)
copyToClipboard(encodedLNURL, 'qr-lnurl') copyToClipboard(encodedLNURL, 'qr-lnurl')
} }
// QR Scanner functions
function closeQRScanner() {
showQRScanner.value = false
}
function handleQRScanResult(result: string) {
// Clean up the scanned result by removing lightning: prefix if present
let cleanedResult = result
if (result.toLowerCase().startsWith('lightning:')) {
cleanedResult = result.substring(10) // Remove "lightning:" prefix
}
// Store the scanned result and close QR scanner
scannedDestination.value = cleanedResult
closeQRScanner()
// Open send dialog with the scanned result
showSendDialog.value = true
toastService?.success('QR code scanned successfully!')
} }
// Initialize on mount // Initialize on mount
@ -211,6 +235,14 @@ onMounted(async () => {
<Send class="h-5 w-5" /> <Send class="h-5 w-5" />
Send Send
</Button> </Button>
<Button
variant="outline"
@click="showQRScanner = true"
class="h-12 w-12 p-0"
title="Scan QR Code"
>
<ScanLine class="h-5 w-5" />
</Button>
</div> </div>
</div> </div>
</CardContent> </CardContent>
@ -517,10 +549,34 @@ onMounted(async () => {
@update:open="showReceiveDialog = $event" @update:open="showReceiveDialog = $event"
/> />
<SendDialog <SendDialog
v-if="showSendDialog" v-if="showSendDialog"
:open="showSendDialog" :open="showSendDialog"
@update:open="showSendDialog = $event" :initial-destination="scannedDestination"
@update:open="(open) => {
showSendDialog = open
if (!open) scannedDestination = ''
}"
/> />
<!-- QR Scanner Dialog -->
<Dialog :open="showQRScanner" @update:open="showQRScanner = $event">
<DialogContent class="sm:max-w-lg">
<DialogHeader>
<DialogTitle class="flex items-center gap-2">
<ScanLine class="h-5 w-5" />
Scan QR Code
</DialogTitle>
<DialogDescription>
Point your camera at a Lightning invoice or payment QR code
</DialogDescription>
</DialogHeader>
<QRScanner
@result="handleQRScanResult"
@close="closeQRScanner"
/>
</DialogContent>
</Dialog>
</div> </div>
</template> </template>