diff --git a/src/components/layout/Navbar.vue b/src/components/layout/Navbar.vue index 2a734ab..a6d7569 100644 --- a/src/components/layout/Navbar.vue +++ b/src/components/layout/Navbar.vue @@ -6,7 +6,7 @@ import { useTheme } from '@/components/theme-provider' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' -import { Sun, Moon, Menu, X, User, LogOut } from 'lucide-vue-next' +import { Sun, Moon, Menu, X, User, LogOut, Ticket } from 'lucide-vue-next' import LanguageSwitcher from '@/components/LanguageSwitcher.vue' import LoginDialog from '@/components/auth/LoginDialog.vue' import ProfileDialog from '@/components/auth/ProfileDialog.vue' @@ -27,6 +27,7 @@ const showProfileDialog = ref(false) const navigation = computed(() => [ { name: t('nav.home'), href: '/' }, { name: t('nav.events'), href: '/events' }, + { name: 'My Tickets', href: '/my-tickets' }, { name: t('nav.support'), href: '/support' }, ]) @@ -98,6 +99,10 @@ const handleLogout = async () => { Profile + + + My Tickets + diff --git a/src/composables/useUserTickets.ts b/src/composables/useUserTickets.ts new file mode 100644 index 0000000..b9c80a9 --- /dev/null +++ b/src/composables/useUserTickets.ts @@ -0,0 +1,77 @@ +import { ref, computed } from 'vue' +import { useAsyncState } from '@vueuse/core' +import type { Ticket } from '@/lib/types/event' +import { fetchUserTickets } from '@/lib/api/events' +import { useAuth } from './useAuth' + +export function useUserTickets() { + const { isAuthenticated, currentUser } = useAuth() + + const { state: tickets, isLoading, error: asyncError, execute: refresh } = useAsyncState( + async () => { + if (!isAuthenticated.value || !currentUser.value) { + return [] + } + return await fetchUserTickets(currentUser.value.id) + }, + [] as Ticket[], + { + immediate: false, + resetOnExecute: false, + } + ) + + const error = computed(() => { + if (asyncError.value) { + return { + message: asyncError.value instanceof Error + ? asyncError.value.message + : 'An error occurred while fetching tickets' + } + } + return null + }) + + const sortedTickets = computed(() => { + return [...tickets.value].sort((a, b) => + new Date(b.time).getTime() - new Date(a.time).getTime() + ) + }) + + const paidTickets = computed(() => { + return sortedTickets.value.filter(ticket => ticket.paid) + }) + + const pendingTickets = computed(() => { + return sortedTickets.value.filter(ticket => !ticket.paid) + }) + + const registeredTickets = computed(() => { + return sortedTickets.value.filter(ticket => ticket.registered) + }) + + const unregisteredTickets = computed(() => { + return sortedTickets.value.filter(ticket => ticket.paid && !ticket.registered) + }) + + // Load tickets when authenticated + const loadTickets = async () => { + if (isAuthenticated.value && currentUser.value) { + await refresh() + } + } + + return { + // State + tickets: sortedTickets, + paidTickets, + pendingTickets, + registeredTickets, + unregisteredTickets, + isLoading, + error, + + // Actions + refresh: loadTickets, + } +} \ No newline at end of file diff --git a/src/lib/api/events.ts b/src/lib/api/events.ts index 68eb6cf..d9b1ec9 100644 --- a/src/lib/api/events.ts +++ b/src/lib/api/events.ts @@ -1,4 +1,4 @@ -import type { Event, EventsApiError } from '../types/event' +import type { Event, EventsApiError, Ticket } from '../types/event' import { config } from '@/lib/config' import { lnbitsAPI } from './lnbits' @@ -131,4 +131,32 @@ export async function checkPaymentStatus(eventId: string, paymentHash: string): console.error('Error checking payment status:', error) throw error } +} + +export async function fetchUserTickets(userId: string): Promise { + try { + 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 index 54e8c99..65a965b 100644 --- a/src/lib/types/event.ts +++ b/src/lib/types/event.ts @@ -14,6 +14,19 @@ export interface Event { 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] diff --git a/src/pages/MyTickets.vue b/src/pages/MyTickets.vue new file mode 100644 index 0000000..b5c6539 --- /dev/null +++ b/src/pages/MyTickets.vue @@ -0,0 +1,269 @@ + + + + \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index b08cdaa..1ed3bca 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -30,6 +30,15 @@ const router = createRouter({ title: 'Events', requiresAuth: true } + }, + { + path: '/my-tickets', + name: 'my-tickets', + component: () => import('@/pages/MyTickets.vue'), + meta: { + title: 'My Tickets', + requiresAuth: true + } } ] })