Enhance CreateEventDialog with dynamic currency selection and improved validation
- Integrated currency selection using a dropdown, replacing the static input field for currency. - Removed the wallet input field, now auto-selecting the preferred wallet for event creation. - Updated form validation to remove the wallet and closing date fields, ensuring ticket sales close when the event ends. - Added functionality to load available currencies from the EventsApiService when the dialog opens, improving user experience. These changes streamline the event creation process and enhance the overall usability of the CreateEventDialog component.
This commit is contained in:
parent
1544126d17
commit
f7ac12bf76
3 changed files with 112 additions and 50 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
|
@ -22,8 +22,17 @@ import {
|
|||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Calendar, Loader2 } from 'lucide-vue-next'
|
||||
import { toastService } from '@/core/services/ToastService'
|
||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||
import { EVENTS_API_TOKEN } from '../composables/useEvents'
|
||||
import type { CreateEventRequest } from '../types/event'
|
||||
|
||||
// Props
|
||||
|
|
@ -38,25 +47,23 @@ const emit = defineEmits<{
|
|||
'event-created': []
|
||||
}>()
|
||||
|
||||
// Form validation schema
|
||||
// Form validation schema (removed wallet field - will be auto-selected)
|
||||
// Note: Ticket sales will automatically close when the event ends
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
wallet: z.string().min(1, "Wallet ID is required"),
|
||||
name: z.string().min(1, "Event name is required").max(200, "Name too long"),
|
||||
info: z.string().min(1, "Event description is required").max(2000, "Description too long"),
|
||||
closing_date: z.string().min(1, "Ticket sale closing date is required"),
|
||||
event_start_date: z.string().min(1, "Event start date is required"),
|
||||
event_end_date: z.string().min(1, "Event end date is required"),
|
||||
currency: z.string().default("sat"),
|
||||
currency: z.string().default("sats"),
|
||||
amount_tickets: z.number().min(1, "Must have at least 1 ticket").max(100000, "Too many tickets"),
|
||||
price_per_ticket: z.number().min(0, "Price must be 0 or higher"),
|
||||
banner: z.string().optional(),
|
||||
}).refine((data) => {
|
||||
const closingDate = new Date(data.closing_date)
|
||||
const startDate = new Date(data.event_start_date)
|
||||
const endDate = new Date(data.event_end_date)
|
||||
return closingDate <= startDate && startDate <= endDate
|
||||
return startDate <= endDate
|
||||
}, {
|
||||
message: "Dates must be in order: closing ≤ start ≤ end",
|
||||
message: "Event start date must be before or equal to end date",
|
||||
path: ["event_end_date"]
|
||||
}))
|
||||
|
||||
|
|
@ -64,19 +71,42 @@ const formSchema = toTypedSchema(z.object({
|
|||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
wallet: '',
|
||||
name: '',
|
||||
info: '',
|
||||
closing_date: '',
|
||||
event_start_date: '',
|
||||
event_end_date: '',
|
||||
currency: 'sat',
|
||||
currency: 'sats',
|
||||
amount_tickets: 100,
|
||||
price_per_ticket: 1000,
|
||||
banner: ''
|
||||
}
|
||||
})
|
||||
|
||||
// Get PaymentService for wallet selection
|
||||
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE)
|
||||
|
||||
// Get EventsApiService for currency loading
|
||||
const eventsApi = injectService(EVENTS_API_TOKEN)
|
||||
|
||||
// Load available currencies
|
||||
const availableCurrencies = ref<string[]>(['sats'])
|
||||
const loadingCurrencies = ref(false)
|
||||
|
||||
// Load currencies when dialog opens
|
||||
watch(() => props.open, async (isOpen) => {
|
||||
if (isOpen && eventsApi && !loadingCurrencies.value) {
|
||||
loadingCurrencies.value = true
|
||||
try {
|
||||
availableCurrencies.value = await eventsApi.getCurrencies()
|
||||
} catch (error) {
|
||||
console.warn('Failed to load currencies:', error)
|
||||
// Keep default currencies
|
||||
} finally {
|
||||
loadingCurrencies.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { resetForm, values, meta } = form
|
||||
const isFormValid = computed(() => meta.value.valid)
|
||||
const isLoading = ref(false)
|
||||
|
|
@ -88,9 +118,27 @@ const today = computed(() => format(new Date(), 'yyyy-MM-dd'))
|
|||
const onSubmit = form.handleSubmit(async (formValues) => {
|
||||
if (!isFormValid.value) return
|
||||
|
||||
if (!paymentService) {
|
||||
toastService.error('Payment service not available')
|
||||
return
|
||||
}
|
||||
|
||||
const preferredWallet = paymentService.getPreferredWallet()
|
||||
if (!preferredWallet) {
|
||||
toastService.error('No wallet available. Please connect a wallet first.')
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
await props.onCreateEvent(formValues as CreateEventRequest)
|
||||
// Add the selected wallet ID and set closing_date to event_end_date
|
||||
const eventData: CreateEventRequest = {
|
||||
...formValues,
|
||||
wallet: preferredWallet.id,
|
||||
closing_date: formValues.event_end_date // Ticket sales close when event ends
|
||||
}
|
||||
|
||||
await props.onCreateEvent(eventData)
|
||||
toastService.success('Event created successfully!')
|
||||
resetForm()
|
||||
emit('update:open', false)
|
||||
|
|
@ -126,21 +174,6 @@ const handleOpenChange = (open: boolean) => {
|
|||
</DialogHeader>
|
||||
|
||||
<form @submit="onSubmit" class="space-y-6 mt-4">
|
||||
<!-- Wallet ID -->
|
||||
<FormField v-slot="{ componentField }" name="wallet">
|
||||
<FormItem>
|
||||
<FormLabel>Wallet ID *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter your LNbits wallet ID"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>Your LNbits wallet ID (admin key required)</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Event Name -->
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
|
|
@ -172,22 +205,7 @@ const handleOpenChange = (open: boolean) => {
|
|||
</FormField>
|
||||
|
||||
<!-- Date Fields -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<FormField v-slot="{ componentField }" name="closing_date">
|
||||
<FormItem>
|
||||
<FormLabel>Ticket Sales Close *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="date"
|
||||
:min="today"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>When ticket sales end</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField v-slot="{ componentField }" name="event_start_date">
|
||||
<FormItem>
|
||||
<FormLabel>Event Starts *</FormLabel>
|
||||
|
|
@ -255,12 +273,25 @@ const handleOpenChange = (open: boolean) => {
|
|||
<FormItem>
|
||||
<FormLabel>Currency</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="sat"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
<Select v-bind="componentField">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select currency" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="currency in availableCurrencies"
|
||||
:key="currency"
|
||||
:value="currency"
|
||||
>
|
||||
{{ currency }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormDescription>Default: sat</FormDescription>
|
||||
<FormDescription>
|
||||
<span v-if="loadingCurrencies">Loading currencies...</span>
|
||||
<span v-else>Currency for ticket pricing</span>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
|
|
|||
|
|
@ -182,4 +182,35 @@ export class EventsApiService {
|
|||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrencies(): Promise<string[]> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.config.baseUrl}/api/v1/currencies`,
|
||||
{
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
// If API call fails, return default currencies
|
||||
console.warn('Failed to fetch currencies from API, using defaults')
|
||||
return ['sats']
|
||||
}
|
||||
|
||||
const apiCurrencies = await response.json()
|
||||
|
||||
// Combine 'sats' with API currencies, following the pattern from market API
|
||||
if (Array.isArray(apiCurrencies)) {
|
||||
return ['sats', ...apiCurrencies.filter((currency: string) => currency !== 'sats')]
|
||||
}
|
||||
|
||||
return ['sats']
|
||||
} catch (error) {
|
||||
console.error('Error fetching currencies:', error)
|
||||
return ['sats']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -331,7 +331,7 @@ export class NostrmarketAPI extends BaseService {
|
|||
* Get available currencies
|
||||
*/
|
||||
async getCurrencies(): Promise<string[]> {
|
||||
const baseCurrencies = ['sat']
|
||||
const baseCurrencies = ['sats']
|
||||
|
||||
try {
|
||||
const apiCurrencies = await this.request<string[]>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue