diff --git a/.env b/.env index e31ed75..ccaca79 100644 --- a/.env +++ b/.env @@ -2,3 +2,6 @@ VITE_SUPPORT_NPUB=npub1tm42jkmdn54zncjcylp34e85jagmgndr0skw4v0rsg8rucmu7r5swayth3 VITE_NOSTR_RELAYS=["wss://nostr.atitlan.io"] + +VITE_API_BASE_URL=https://lnbits.ariege.io +VITE_API_KEY=8453f8a467ea47cfa1a47b358413dd0a diff --git a/package-lock.json b/package-lock.json index cd20a34..fc101ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "nostr-tools": "^2.10.4", "pinia": "^2.3.1", "radix-vue": "^1.9.13", + "reka-ui": "^2.0.2", "tailwind-merge": "^2.6.0", "tailwind-variants": "^0.3.1", "tailwindcss-animate": "^1.0.7", @@ -3299,9 +3300,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.3.tgz", - "integrity": "sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.2.tgz", + "integrity": "sha512-Qzz4EgzMbO5gKrmqUondCjiHcuu4B1ftHb0pjCut661lXZdGoHeze9f/M8iwsK1t5LGR6aNuNGU7mxkowaW6RQ==", "license": "MIT", "funding": { "type": "github", @@ -3309,12 +3310,12 @@ } }, "node_modules/@tanstack/vue-virtual": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.11.3.tgz", - "integrity": "sha512-BVZ00i5XBucetRj2doVd32jOPtJthvZSVJvx9GL4gSQsyngliSCtzlP1Op7TFrEtmebRKT8QUQE1tRhOQzWecQ==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.2.tgz", + "integrity": "sha512-z4swzjdhzCh95n9dw9lTvw+t3iwSkYRlVkYkra3C9mul/m5fTzHR7KmtkwH4qXMTXGJUbngtC/bz2cHQIHkO8g==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.11.3" + "@tanstack/virtual-core": "3.13.2" }, "funding": { "type": "github", @@ -6336,6 +6337,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", + "integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==", + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6751,6 +6758,39 @@ "node": ">=6" } }, + "node_modules/reka-ui": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.0.2.tgz", + "integrity": "sha512-pC2UF6Z+kJF96aJvIErhkSO4DJYIeq9pgvh3pntNqcZb3zFGMzw8h2uny+GnLX2CKiQV54kZNYXxecYIiPMGyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.13", + "@floating-ui/vue": "^1.1.6", + "@internationalized/date": "^3.5.0", + "@internationalized/number": "^3.5.0", + "@tanstack/vue-virtual": "^3.12.0", + "@vueuse/core": "^12.5.0", + "@vueuse/shared": "^12.5.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.4", + "ohash": "^1.1.4" + }, + "peerDependencies": { + "vue": ">= 3.2.0" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index fe12e39..b57cb0c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "nostr-tools": "^2.10.4", "pinia": "^2.3.1", "radix-vue": "^1.9.13", + "reka-ui": "^2.0.2", "tailwind-merge": "^2.6.0", "tailwind-variants": "^0.3.1", "tailwindcss-animate": "^1.0.7", diff --git a/src/components/layout/Navbar.vue b/src/components/layout/Navbar.vue index 4cb89b1..9ce1b23 100644 --- a/src/components/layout/Navbar.vue +++ b/src/components/layout/Navbar.vue @@ -19,6 +19,7 @@ const isOpen = ref(false) const navigation = computed(() => [ { name: t('nav.home'), href: '/' }, + { name: t('nav.events'), href: '/events' }, { name: t('nav.support'), href: '/support' }, ]) diff --git a/src/components/ui/tabs/Tabs.vue b/src/components/ui/tabs/Tabs.vue new file mode 100644 index 0000000..15aeca8 --- /dev/null +++ b/src/components/ui/tabs/Tabs.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/tabs/TabsList.vue b/src/components/ui/tabs/TabsList.vue new file mode 100644 index 0000000..d5c79ef --- /dev/null +++ b/src/components/ui/tabs/TabsList.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/components/ui/tabs/index.ts b/src/components/ui/tabs/index.ts new file mode 100644 index 0000000..a5e58dc --- /dev/null +++ b/src/components/ui/tabs/index.ts @@ -0,0 +1,4 @@ +export { default as Tabs } from './Tabs.vue' +export { default as TabsContent } from './TabsContent.vue' +export { default as TabsList } from './TabsList.vue' +export { default as TabsTrigger } from './TabsTrigger.vue' diff --git a/src/composables/useEvents.ts b/src/composables/useEvents.ts new file mode 100644 index 0000000..4dc34f1 --- /dev/null +++ b/src/composables/useEvents.ts @@ -0,0 +1,44 @@ +import { ref, computed } from 'vue' +import { useAsyncState } from '@vueuse/core' +import type { Event } from '@/lib/types/event' +import { fetchEvents } from '@/lib/api/events' + +export function useEvents() { + const { state: events, isLoading, error, execute: refresh } = useAsyncState( + fetchEvents, + [] as Event[], + { + immediate: true, + resetOnExecute: false, + } + ) + + const sortedEvents = computed(() => { + return [...events.value].sort((a, b) => + new Date(b.time).getTime() - new Date(a.time).getTime() + ) + }) + + const upcomingEvents = computed(() => { + const now = new Date() + return sortedEvents.value.filter(event => + new Date(event.event_start_date) > now + ) + }) + + const pastEvents = computed(() => { + const now = new Date() + return sortedEvents.value.filter(event => + new Date(event.event_end_date) < now + ) + }) + + return { + events: sortedEvents, + upcomingEvents, + pastEvents, + isLoading, + error, + refresh, + } +} \ No newline at end of file diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 91ba4cb..5f11bc7 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -7,6 +7,7 @@ const messages: LocaleMessages = { directory: 'Directory', faq: 'FAQ', support: 'Support', + events: 'Events', login: 'Login', logout: 'Logout' }, diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 729e969..2330883 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -5,6 +5,7 @@ export interface LocaleMessages { directory: string faq: string support: string + events: string login: string logout: string } diff --git a/src/lib/api/events.ts b/src/lib/api/events.ts new file mode 100644 index 0000000..fac98d5 --- /dev/null +++ b/src/lib/api/events.ts @@ -0,0 +1,28 @@ +import type { Event, EventsApiError } from '../types/event' + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://lnbits' +const API_KEY = import.meta.env.VITE_API_KEY + +export async function fetchEvents(allWallets = true): Promise { + try { + const response = await fetch( + `${API_BASE_URL}/events/api/v1/events?all_wallets=${allWallets}`, + { + headers: { + 'accept': 'application/json', + 'X-API-KEY': API_KEY, + }, + } + ) + + if (!response.ok) { + const error: EventsApiError = await response.json() + throw new Error(error.detail[0]?.msg || 'Failed to fetch events') + } + + return await response.json() as Event[] + } catch (error) { + console.error('Error fetching events:', error) + throw error + } +} \ No newline at end of file diff --git a/src/lib/types/event.ts b/src/lib/types/event.ts new file mode 100644 index 0000000..54e8c99 --- /dev/null +++ b/src/lib/types/event.ts @@ -0,0 +1,23 @@ +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 EventsApiError { + detail: Array<{ + loc: [string, number] + msg: string + type: string + }> +} \ No newline at end of file diff --git a/src/pages/events.vue b/src/pages/events.vue new file mode 100644 index 0000000..d97aa30 --- /dev/null +++ b/src/pages/events.vue @@ -0,0 +1,115 @@ + + + + \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 7b6a5f5..6aafe87 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -9,6 +9,14 @@ const router = createRouter({ name: 'home', component: Home }, + { + path: '/events', + name: 'events', + component: () => import('@/pages/events.vue'), + meta: { + title: 'Events' + } + } ] })