diff --git a/src/app.config.ts b/src/app.config.ts index a414bd2..da83333 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -88,19 +88,6 @@ export const appConfig: AppConfig = { } } }, - events: { - name: 'events', - enabled: false, - lazy: false, - config: { - apiConfig: { - baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || 'http://localhost:5000', - apiKey: import.meta.env.VITE_API_KEY || '' - }, - ticketValidationEndpoint: '/api/tickets/validate', - maxTicketsPerUser: 10 - } - }, activities: { name: 'activities', enabled: true, diff --git a/src/app.ts b/src/app.ts index d5281b5..f23f021 100644 --- a/src/app.ts +++ b/src/app.ts @@ -13,7 +13,6 @@ import appConfig from './app.config' import baseModule from './modules/base' import nostrFeedModule from './modules/nostr-feed' import chatModule from './modules/chat' -import eventsModule from './modules/events' import marketModule from './modules/market' import walletModule from './modules/wallet' import expensesModule from './modules/expenses' @@ -44,7 +43,6 @@ export async function createAppInstance() { ...baseModule.routes || [], ...nostrFeedModule.routes || [], ...chatModule.routes || [], - ...eventsModule.routes || [], ...marketModule.routes || [], ...walletModule.routes || [], ...expensesModule.routes || [], @@ -113,13 +111,6 @@ export async function createAppInstance() { ) } - // Register events module - if (appConfig.modules.events.enabled) { - moduleRegistrations.push( - pluginManager.register(eventsModule, appConfig.modules.events) - ) - } - // Register market module if (appConfig.modules.market.enabled) { moduleRegistrations.push( diff --git a/src/composables/useModularNavigation.ts b/src/composables/useModularNavigation.ts index b03bbe8..a29efd2 100644 --- a/src/composables/useModularNavigation.ts +++ b/src/composables/useModularNavigation.ts @@ -26,11 +26,11 @@ export function useModularNavigation() { items.push({ name: t('nav.home'), href: '/', requiresAuth: true }) // Add navigation items based on enabled modules - if (appConfig.modules.events.enabled) { - items.push({ - name: t('nav.events'), - href: '/events', - requiresAuth: true + if (appConfig.modules.activities?.enabled) { + items.push({ + name: t('nav.events'), + href: '/events', + requiresAuth: true }) } @@ -67,8 +67,8 @@ export function useModularNavigation() { const userMenuItems = computed(() => { const items: NavigationItem[] = [] - // Events module items - if (appConfig.modules.events.enabled) { + // Activities module items (events + tickets) + if (appConfig.modules.activities?.enabled) { items.push({ name: 'My Tickets', href: '/my-tickets', diff --git a/src/core/di-container.ts b/src/core/di-container.ts index bcd8177..411ebad 100644 --- a/src/core/di-container.ts +++ b/src/core/di-container.ts @@ -149,12 +149,10 @@ export const SERVICE_TOKENS = { // Nostr metadata services NOSTR_METADATA_SERVICE: Symbol('nostrMetadataService'), - // Events services - EVENTS_SERVICE: Symbol('eventsService'), - - // Activities services (Nostr-native events module) + // Activities services (Nostr-native events + ticketing module) ACTIVITIES_NOSTR_SERVICE: Symbol('activitiesNostrService'), ACTIVITIES_TICKET_API: Symbol('activitiesTicketApi'), + TICKET_API: Symbol('ticketApi'), // Invoice services INVOICE_SERVICE: Symbol('invoiceService'), diff --git a/src/core/services/PaymentService.ts b/src/core/services/PaymentService.ts index 8f5733b..0021e46 100644 --- a/src/core/services/PaymentService.ts +++ b/src/core/services/PaymentService.ts @@ -1,6 +1,6 @@ import { ref, computed } from 'vue' import { BaseService } from '@/core/base/BaseService' -import { payInvoiceWithWallet } from '@/lib/api/events' +import { config } from '@/lib/config' import { toast } from 'vue-sonner' export interface PaymentResult { @@ -198,6 +198,41 @@ export class PaymentService extends BaseService { } } + /** + * Pay a Lightning invoice via LNbits POST /api/v1/payments. + * This is a core LNbits operation, not extension-specific. + */ + private async payInvoice( + paymentRequest: string, + adminKey: string + ): Promise { + const baseUrl = config.api.baseUrl + const response = await fetch(`${baseUrl}/api/v1/payments`, { + method: 'POST', + headers: { + 'accept': 'application/json', + 'Content-Type': 'application/json', + 'X-API-KEY': adminKey, + }, + body: JSON.stringify({ + out: true, + bolt11: paymentRequest, + }), + }) + + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: 'Payment failed' })) + const errorMessage = typeof error.detail === 'string' + ? error.detail + : Array.isArray(error.detail) + ? error.detail[0]?.msg ?? 'Payment failed' + : 'Payment failed' + throw new Error(errorMessage) + } + + return await response.json() + } + /** * Pay Lightning invoice with user's wallet */ @@ -224,9 +259,8 @@ export class PaymentService extends BaseService { this.debug(`Paying invoice with wallet: ${wallet.id.slice(0, 8)}`) // Make payment - const paymentResult = await payInvoiceWithWallet( - paymentRequest, - wallet.id, + const paymentResult = await this.payInvoice( + paymentRequest, wallet.adminkey ) diff --git a/src/lib/api/events.ts b/src/lib/api/events.ts deleted file mode 100644 index dbb783c..0000000 --- a/src/lib/api/events.ts +++ /dev/null @@ -1,169 +0,0 @@ -import type { Event, Ticket } from '../types/event' -import { config } from '@/lib/config' -import { injectService, SERVICE_TOKENS } from '@/core/di-container' -import type { LnbitsAPI } from './lnbits' - -const API_BASE_URL = config.api.baseUrl || 'http://lnbits' -const API_KEY = config.api.key - -// Generic error type for API responses -interface ApiError { - detail: string | Array<{ loc: [string, number]; msg: string; type: string }> -} - -export async function fetchEvents(): Promise { - try { - // Use the new public endpoint that allows access to all events without authentication - const response = await fetch( - `${API_BASE_URL}/events/api/v1/events/public`, - { - headers: { - 'accept': 'application/json', - }, - } - ) - - if (!response.ok) { - const error: ApiError = await response.json() - const errorMessage = typeof error.detail === 'string' - ? error.detail - : error.detail[0]?.msg || 'Failed to fetch events' - throw new Error(errorMessage) - } - - return await response.json() as Event[] - } catch (error) { - console.error('Error fetching events:', error) - throw error - } -} - -export async function purchaseTicket(eventId: string): Promise<{ payment_hash: string; payment_request: string }> { - try { - // Get injected LnbitsAPI service - const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) as LnbitsAPI - - // Get current user to ensure authentication - const user = await lnbitsAPI.getCurrentUser() - if (!user) { - throw new Error('User not authenticated') - } - - const response = await fetch( - `${API_BASE_URL}/events/api/v1/tickets/${eventId}/user/${user.id}`, - { - method: 'GET', - headers: { - 'accept': 'application/json', - 'X-API-KEY': API_KEY, - 'Authorization': `Bearer ${lnbitsAPI.getAccessToken()}`, - }, - } - ) - - if (!response.ok) { - const error: ApiError = await response.json() - const errorMessage = typeof error.detail === 'string' - ? error.detail - : error.detail[0]?.msg || 'Failed to purchase ticket' - throw new Error(errorMessage) - } - - return await response.json() - } catch (error) { - console.error('Error purchasing ticket:', error) - throw error - } -} - -export async function payInvoiceWithWallet(paymentRequest: string, _walletId: string, adminKey: string): Promise<{ payment_hash: string; fee_msat: number; preimage: string }> { - try { - const response = await fetch( - `${API_BASE_URL}/api/v1/payments`, - { - method: 'POST', - headers: { - 'accept': 'application/json', - 'Content-Type': 'application/json', - 'X-API-KEY': adminKey, - }, - body: JSON.stringify({ - out: true, - bolt11: paymentRequest, - }), - } - ) - - if (!response.ok) { - const error: ApiError = await response.json() - const errorMessage = typeof error.detail === 'string' - ? error.detail - : error.detail[0]?.msg || 'Failed to pay invoice' - throw new Error(errorMessage) - } - - return await response.json() - } catch (error) { - console.error('Error paying invoice:', error) - throw error - } -} - -export async function checkPaymentStatus(eventId: string, paymentHash: string): Promise<{ paid: boolean; ticket_id?: string }> { - try { - const response = await fetch( - `${API_BASE_URL}/events/api/v1/tickets/${eventId}/${paymentHash}`, - { - method: 'POST', - headers: { - 'accept': 'application/json', - 'X-API-KEY': API_KEY, - }, - } - ) - - if (!response.ok) { - const error: ApiError = await response.json() - const errorMessage = typeof error.detail === 'string' - ? error.detail - : error.detail[0]?.msg || 'Failed to check payment status' - throw new Error(errorMessage) - } - - return await response.json() - } catch (error) { - console.error('Error checking payment status:', error) - throw error - } -} - -export async function fetchUserTickets(userId: string): Promise { - try { - // Get injected LnbitsAPI service - const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) as LnbitsAPI - - const response = await fetch( - `${API_BASE_URL}/events/api/v1/tickets/user/${userId}`, - { - headers: { - 'accept': 'application/json', - 'X-API-KEY': API_KEY, - 'Authorization': `Bearer ${lnbitsAPI.getAccessToken()}`, - }, - } - ) - - if (!response.ok) { - const error: ApiError = await response.json() - const errorMessage = typeof error.detail === 'string' - ? error.detail - : error.detail[0]?.msg || 'Failed to fetch user tickets' - throw new Error(errorMessage) - } - - return await response.json() - } catch (error) { - console.error('Error fetching user tickets:', error) - throw error - } -} \ No newline at end of file diff --git a/src/lib/types/event.ts b/src/lib/types/event.ts deleted file mode 100644 index 65a965b..0000000 --- a/src/lib/types/event.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface Event { - id: string - wallet: string - name: string - info: string - closing_date: string - event_start_date: string - event_end_date: string - currency: string - amount_tickets: number - price_per_ticket: number - time: string - sold: number - banner: string | null -} - -export interface Ticket { - id: string - wallet: string - event: string - name: string | null - email: string | null - user_id: string | null - registered: boolean - paid: boolean - time: string - reg_timestamp: string -} - -export interface EventsApiError { - detail: Array<{ - loc: [string, number] - msg: string - type: string - }> -} \ No newline at end of file diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/activities/components/CreateEventDialog.vue similarity index 72% rename from src/modules/events/components/CreateEventDialog.vue rename to src/modules/activities/components/CreateEventDialog.vue index 75bf327..774514f 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/activities/components/CreateEventDialog.vue @@ -32,10 +32,9 @@ import { 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' +import type { TicketApiService } from '../services/TicketApiService' +import type { CreateEventRequest } from '../types/ticket' -// Props interface Props { open: boolean onCreateEvent: (eventData: CreateEventRequest) => Promise @@ -47,8 +46,6 @@ const emit = defineEmits<{ 'event-created': [] }>() -// 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({ 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"), @@ -67,7 +64,6 @@ const formSchema = toTypedSchema(z.object({ path: ["event_end_date"] })) -// Form setup const form = useForm({ validationSchema: formSchema, initialValues: { @@ -82,29 +78,19 @@ const form = useForm({ } }) -// Get PaymentService for wallet selection const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) +const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService | null -// Get EventsApiService for currency loading -const eventsApi = injectService(EVENTS_API_TOKEN) - -// Load available currencies const availableCurrencies = ref(['sats']) const loadingCurrencies = ref(false) -// Load currencies when dialog opens watch(() => props.open, async (isOpen) => { - if (isOpen && eventsApi && !loadingCurrencies.value) { + if (isOpen && ticketApi && !loadingCurrencies.value) { loadingCurrencies.value = true try { - // Type cast to ensure getCurrencies method exists - const apiService = eventsApi as any - if (apiService.getCurrencies) { - availableCurrencies.value = await apiService.getCurrencies() - } + availableCurrencies.value = await ticketApi.getCurrencies() } catch (error) { console.warn('Failed to load currencies:', error) - // Keep default currencies } finally { loadingCurrencies.value = false } @@ -115,35 +101,31 @@ const { resetForm, meta } = form const isFormValid = computed(() => meta.value.valid) const isLoading = ref(false) -// Get today's date in YYYY-MM-DD format for min date validation const today = computed(() => format(new Date(), 'yyyy-MM-dd')) -// Form submission const onSubmit = form.handleSubmit(async (formValues) => { if (!isFormValid.value) return - + if (!paymentService) { toastService.error('Payment service not available') return } - // Type cast to ensure getPreferredWallet method exists const paymentSvc = paymentService as any const preferredWallet = paymentSvc?.getPreferredWallet?.() if (!preferredWallet) { toastService.error('No wallet available. Please connect a wallet first.') return } - + isLoading.value = true try { - // 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 + closing_date: formValues.event_end_date } - + await props.onCreateEvent(eventData) toastService.success('Event created successfully!') resetForm() @@ -157,7 +139,6 @@ const onSubmit = form.handleSubmit(async (formValues) => { } }) -// Handle dialog close const handleOpenChange = (open: boolean) => { if (!open && !isLoading.value) { resetForm() @@ -180,47 +161,33 @@ const handleOpenChange = (open: boolean) => {
- Event Name * - + - Event Description * -