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">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, 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'
|
||||||
|
|
@ -22,8 +22,17 @@ import {
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
import { Calendar, Loader2 } from 'lucide-vue-next'
|
import { Calendar, Loader2 } from 'lucide-vue-next'
|
||||||
import { toastService } from '@/core/services/ToastService'
|
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'
|
import type { CreateEventRequest } from '../types/event'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
|
|
@ -38,25 +47,23 @@ const emit = defineEmits<{
|
||||||
'event-created': []
|
'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({
|
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"),
|
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"),
|
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_start_date: z.string().min(1, "Event start date is required"),
|
||||||
event_end_date: z.string().min(1, "Event end 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"),
|
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"),
|
price_per_ticket: z.number().min(0, "Price must be 0 or higher"),
|
||||||
banner: z.string().optional(),
|
banner: z.string().optional(),
|
||||||
}).refine((data) => {
|
}).refine((data) => {
|
||||||
const closingDate = new Date(data.closing_date)
|
|
||||||
const startDate = new Date(data.event_start_date)
|
const startDate = new Date(data.event_start_date)
|
||||||
const endDate = new Date(data.event_end_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"]
|
path: ["event_end_date"]
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -64,19 +71,42 @@ const formSchema = toTypedSchema(z.object({
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
validationSchema: formSchema,
|
validationSchema: formSchema,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
wallet: '',
|
|
||||||
name: '',
|
name: '',
|
||||||
info: '',
|
info: '',
|
||||||
closing_date: '',
|
|
||||||
event_start_date: '',
|
event_start_date: '',
|
||||||
event_end_date: '',
|
event_end_date: '',
|
||||||
currency: 'sat',
|
currency: 'sats',
|
||||||
amount_tickets: 100,
|
amount_tickets: 100,
|
||||||
price_per_ticket: 1000,
|
price_per_ticket: 1000,
|
||||||
banner: ''
|
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 { resetForm, values, meta } = form
|
||||||
const isFormValid = computed(() => meta.value.valid)
|
const isFormValid = computed(() => meta.value.valid)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
@ -88,9 +118,27 @@ const today = computed(() => format(new Date(), 'yyyy-MM-dd'))
|
||||||
const onSubmit = form.handleSubmit(async (formValues) => {
|
const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
if (!isFormValid.value) return
|
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
|
isLoading.value = true
|
||||||
try {
|
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!')
|
toastService.success('Event created successfully!')
|
||||||
resetForm()
|
resetForm()
|
||||||
emit('update:open', false)
|
emit('update:open', false)
|
||||||
|
|
@ -126,21 +174,6 @@ const handleOpenChange = (open: boolean) => {
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<form @submit="onSubmit" class="space-y-6 mt-4">
|
<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 -->
|
<!-- Event Name -->
|
||||||
<FormField v-slot="{ componentField }" name="name">
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
|
|
@ -172,22 +205,7 @@ const handleOpenChange = (open: boolean) => {
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Date Fields -->
|
<!-- Date Fields -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 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>
|
|
||||||
|
|
||||||
<FormField v-slot="{ componentField }" name="event_start_date">
|
<FormField v-slot="{ componentField }" name="event_start_date">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Event Starts *</FormLabel>
|
<FormLabel>Event Starts *</FormLabel>
|
||||||
|
|
@ -255,12 +273,25 @@ const handleOpenChange = (open: boolean) => {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Currency</FormLabel>
|
<FormLabel>Currency</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Select v-bind="componentField">
|
||||||
placeholder="sat"
|
<SelectTrigger>
|
||||||
v-bind="componentField"
|
<SelectValue placeholder="Select currency" />
|
||||||
/>
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem
|
||||||
|
v-for="currency in availableCurrencies"
|
||||||
|
:key="currency"
|
||||||
|
:value="currency"
|
||||||
|
>
|
||||||
|
{{ currency }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>Default: sat</FormDescription>
|
<FormDescription>
|
||||||
|
<span v-if="loadingCurrencies">Loading currencies...</span>
|
||||||
|
<span v-else>Currency for ticket pricing</span>
|
||||||
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
@ -303,4 +334,4 @@ const handleOpenChange = (open: boolean) => {
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -182,4 +182,35 @@ export class EventsApiService {
|
||||||
throw error
|
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
|
* Get available currencies
|
||||||
*/
|
*/
|
||||||
async getCurrencies(): Promise<string[]> {
|
async getCurrencies(): Promise<string[]> {
|
||||||
const baseCurrencies = ['sat']
|
const baseCurrencies = ['sats']
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiCurrencies = await this.request<string[]>(
|
const apiCurrencies = await this.request<string[]>(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue