- Updated import paths in the wallet module to enhance clarity and maintainability. - Removed unused imports in ReceiveDialog and SendDialog components to streamline the code. - Introduced a computed property in WalletPage to extract the base domain from the payment service configuration, improving readability and error handling. These changes contribute to a cleaner codebase and enhance the overall performance of the wallet module.
173 lines
No EOL
5.3 KiB
Vue
173 lines
No EOL
5.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useForm } from 'vee-validate'
|
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
import * as z from 'zod'
|
|
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 { Send, AlertCircle, Loader2 } from 'lucide-vue-next'
|
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
|
|
|
interface Props {
|
|
open: boolean
|
|
}
|
|
|
|
interface Emits {
|
|
(e: 'update:open', value: boolean): void
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits<Emits>()
|
|
|
|
// Services
|
|
const walletService = injectService(SERVICE_TOKENS.WALLET_SERVICE) as any
|
|
const toastService = injectService(SERVICE_TOKENS.TOAST_SERVICE) as any
|
|
|
|
// Form validation schema
|
|
const formSchema = toTypedSchema(z.object({
|
|
destination: z.string().min(1, "Destination is required"),
|
|
amount: z.number().min(1, "Amount must be at least 1 sat").max(1000000, "Amount too large"),
|
|
comment: z.string().optional()
|
|
}))
|
|
|
|
// Form setup
|
|
const form = useForm({
|
|
validationSchema: formSchema,
|
|
initialValues: {
|
|
destination: '',
|
|
amount: 100,
|
|
comment: ''
|
|
}
|
|
})
|
|
|
|
const { resetForm, values, meta } = form
|
|
const isFormValid = computed(() => meta.value.valid)
|
|
|
|
// State
|
|
const isSending = computed(() => walletService?.isSendingPayment?.value || false)
|
|
const error = computed(() => walletService?.error?.value)
|
|
|
|
// Methods
|
|
const onSubmit = form.handleSubmit(async (formValues) => {
|
|
try {
|
|
const success = await walletService.sendPayment({
|
|
destination: formValues.destination,
|
|
amount: formValues.amount,
|
|
comment: formValues.comment || undefined
|
|
})
|
|
|
|
if (success) {
|
|
toastService?.success('Payment sent successfully!')
|
|
closeDialog()
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to send payment:', error)
|
|
}
|
|
})
|
|
|
|
function closeDialog() {
|
|
emit('update:open', false)
|
|
resetForm()
|
|
}
|
|
|
|
// Determine destination type helper text
|
|
const destinationType = computed(() => {
|
|
const dest = values.destination?.toLowerCase() || ''
|
|
if (dest.startsWith('lnbc') || dest.startsWith('lntb')) {
|
|
return 'Lightning Invoice'
|
|
} else if (dest.includes('@')) {
|
|
return 'Lightning Address'
|
|
} else if (dest.startsWith('lnurl')) {
|
|
return 'LNURL'
|
|
}
|
|
return ''
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Dialog :open="props.open" @update:open="emit('update:open', $event)">
|
|
<DialogContent class="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle class="flex items-center gap-2">
|
|
<Send class="h-5 w-5" />
|
|
Send Bitcoin
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Send Bitcoin via Lightning Network
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<form @submit="onSubmit" class="space-y-4">
|
|
<FormField v-slot="{ componentField }" name="destination">
|
|
<FormItem>
|
|
<FormLabel>Destination *</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
placeholder="Lightning invoice, LNURL, or Lightning address (user@domain.com)"
|
|
v-bind="componentField"
|
|
class="min-h-[80px] font-mono text-xs"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription v-if="destinationType">
|
|
Detected: {{ destinationType }}
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="amount">
|
|
<FormItem>
|
|
<FormLabel>Amount (sats) *</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
min="1"
|
|
placeholder="100"
|
|
v-bind="componentField"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>Amount to send in satoshis</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="comment">
|
|
<FormItem>
|
|
<FormLabel>Comment (Optional)</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="Optional message with payment"
|
|
v-bind="componentField"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>Add a note to your payment (if supported)</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<div v-if="error" class="flex items-start gap-2 p-3 bg-destructive/15 text-destructive rounded-lg">
|
|
<AlertCircle class="h-4 w-4 mt-0.5" />
|
|
<span class="text-sm">{{ error }}</span>
|
|
</div>
|
|
|
|
<div class="flex gap-2 pt-4">
|
|
<Button
|
|
type="submit"
|
|
:disabled="!isFormValid || isSending"
|
|
class="flex-1"
|
|
>
|
|
<Loader2 v-if="isSending" class="w-4 h-4 mr-2 animate-spin" />
|
|
<Send v-else class="w-4 h-4 mr-2" />
|
|
{{ isSending ? 'Sending...' : 'Send Payment' }}
|
|
</Button>
|
|
<Button type="button" variant="outline" @click="closeDialog" :disabled="isSending">
|
|
Cancel
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template> |