diff --git a/.env.example b/.env.example index ecae912..99327b7 100644 --- a/.env.example +++ b/.env.example @@ -11,13 +11,6 @@ VITE_API_KEY=your-api-key-here VITE_LNBITS_DEBUG=false VITE_WEBSOCKET_ENABLED=true -# LNbits Nostr-transport server pubkey (kind-21000 RPC endpoint). -# Logged by the LNbits server at startup: -# `Nostr transport: starting with pubkey ... on N relay(s)` -# Required for the activities ticket scanner; legacy HTTP path still -# works without it. -VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY= - # Lightning Address Domain (optional) # Override the domain used for Lightning Addresses # If not set, domain will be extracted from VITE_LNBITS_BASE_URL diff --git a/src/core/di-container.ts b/src/core/di-container.ts index f6c87cd..15aa084 100644 --- a/src/core/di-container.ts +++ b/src/core/di-container.ts @@ -149,9 +149,6 @@ export const SERVICE_TOKENS = { // Nostr metadata services NOSTR_METADATA_SERVICE: Symbol('nostrMetadataService'), - // Nostr transport (kind-21000 RPC over relays — LNbits backend) - NOSTR_TRANSPORT_SERVICE: Symbol('nostrTransportService'), - // Activities services (Nostr-native events + ticketing module) ACTIVITIES_NOSTR_SERVICE: Symbol('activitiesNostrService'), ACTIVITIES_TICKET_API: Symbol('activitiesTicketApi'), diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 0c529a2..38bb64a 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -58,7 +58,6 @@ const messages: LocaleMessages = { thisWeek: 'This Week', thisMonth: 'This Month', myTickets: 'My tickets', - hosting: 'Hosting', }, categories: { concert: 'Concert', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index e72be71..b2603e9 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -58,7 +58,6 @@ const messages: LocaleMessages = { thisWeek: 'Esta semana', thisMonth: 'Este mes', myTickets: 'Mis boletos', - hosting: 'Organizo', }, categories: { concert: 'Concierto', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 3b6ed76..388f602 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -58,7 +58,6 @@ const messages: LocaleMessages = { thisWeek: 'Cette semaine', thisMonth: 'Ce mois-ci', myTickets: 'Mes billets', - hosting: 'J\'organise', }, categories: { concert: 'Concert', diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 4e5584d..d44f236 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -59,7 +59,6 @@ export interface LocaleMessages { thisWeek: string thisMonth: string myTickets: string - hosting: string } categories: Record detail: { diff --git a/src/lib/config/lnbits.ts b/src/lib/config/lnbits.ts index cc33aff..dec6c8e 100644 --- a/src/lib/config/lnbits.ts +++ b/src/lib/config/lnbits.ts @@ -4,12 +4,6 @@ export const LNBITS_CONFIG = { // This should point to your LNBits instance API_BASE_URL: `${import.meta.env.VITE_LNBITS_BASE_URL || ''}/api/v1`, - // LNbits Nostr-transport server pubkey. The webapp encrypts its - // signed kind-21000 RPC events to this pubkey and listens for - // signed responses from it. Logged by the LNbits server at startup - // (`Nostr transport: starting with pubkey ...`). - NOSTR_TRANSPORT_PUBKEY: import.meta.env.VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY || '', - // Whether to enable debug logging DEBUG: import.meta.env.VITE_LNBITS_DEBUG === 'true', diff --git a/src/modules/activities/composables/useActivityFilters.ts b/src/modules/activities/composables/useActivityFilters.ts index 4aba255..60bcb5e 100644 --- a/src/modules/activities/composables/useActivityFilters.ts +++ b/src/modules/activities/composables/useActivityFilters.ts @@ -22,13 +22,6 @@ export function useActivityFilters() { * (this composable stays free of ticket fetching). */ const onlyOwnedTickets = ref(false) - /** - * When true, the feed is narrowed to activities the current user - * is hosting (organizer pubkey matches the signed-in user, or the - * row is a local LNbits draft of theirs). Reads `activity.isMine` - * which `useActivities.tagOwnership()` populates. - */ - const onlyHosting = ref(false) const filters = computed(() => ({ temporal: temporal.value, @@ -61,13 +54,6 @@ export function useActivityFilters() { ) } - // Hosting filter — activities the signed-in user organizes. - // Read off `activity.isMine` which `useActivities.tagOwnership()` - // populates from organizer-pubkey match + LNbits drafts. - if (onlyHosting.value) { - result = result.filter(a => a.isMine === true) - } - return result } @@ -103,23 +89,17 @@ export function useActivityFilters() { selectedCategories.value = [] selectedDate.value = undefined onlyOwnedTickets.value = false - onlyHosting.value = false } function toggleOwnedTickets() { onlyOwnedTickets.value = !onlyOwnedTickets.value } - function toggleHosting() { - onlyHosting.value = !onlyHosting.value - } - const hasActiveFilters = computed(() => temporal.value !== 'all' || selectedCategories.value.length > 0 || selectedDate.value !== undefined || - onlyOwnedTickets.value || - onlyHosting.value + onlyOwnedTickets.value ) return { @@ -128,7 +108,6 @@ export function useActivityFilters() { selectedCategories, selectedDate, onlyOwnedTickets, - onlyHosting, filters, hasActiveFilters, @@ -139,7 +118,6 @@ export function useActivityFilters() { toggleCategory, clearCategories, toggleOwnedTickets, - toggleHosting, resetFilters, } } diff --git a/src/modules/activities/composables/useTicketScanner.ts b/src/modules/activities/composables/useTicketScanner.ts deleted file mode 100644 index 50006a1..0000000 --- a/src/modules/activities/composables/useTicketScanner.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { ref, type Ref } from 'vue' -import { useLocalStorage } from '@vueuse/core' -import { injectService, SERVICE_TOKENS } from '@/core/di-container' -import type { NostrTransportService } from '@/modules/base/services/NostrTransportService' - -export type ScanStatus = 'ok' | 'duplicate-session' | 'error' - -export interface ScanResult { - status: ScanStatus - ticketId: string - /** Backend response payload on OK. */ - ticket?: Record - /** Error string from the backend or local validation. */ - message?: string -} - -export interface ScanRecord { - ticketId: string - /** Holder display name from the backend, if any. */ - name?: string | null - registeredAt: string -} - -/** - * Stateful scanner driver. Owns the camera lifecycle (delegated to - * useQRScanner upstream), the QR decode, the - * `events_ticket_register` RPC call, and a session-local scanned - * list persisted to localStorage so a page refresh doesn't ask the - * organizer to rescan tickets they already counted. - * - * Mirrors the LNbits admin Quasar register page's - * `events_scanned_` localStorage key with the - * `activities_scanned_` prefix. - */ -export function useTicketScanner(activityId: Ref) { - const transport = injectService( - SERVICE_TOKENS.NOSTR_TRANSPORT_SERVICE, - ) - - const isProcessing = ref(false) - const lastScan = ref(null) - /** - * Set to `true` immediately after a decode resolves (success or - * failure) and stays true until the operator dismisses or taps - * "Scan next". While paused, further `onDecode` calls are dropped - * — the camera keeps streaming for instant resume but the result - * banner sticks so the door can confirm the outcome before - * moving on. Without this, the 5-fps decode loop instantly - * fires "already scanned this session" on the very ticket that - * just succeeded. - */ - const isPaused = ref(false) - const scanned = useLocalStorage( - () => `activities_scanned_${activityId.value}`, - [], - ) - - function parseTicketId(qrText: string): string { - return qrText.startsWith('ticket://') - ? qrText.slice('ticket://'.length) - : qrText - } - - async function onDecode(qrText: string): Promise { - if (isProcessing.value || isPaused.value) return - const ticketId = parseTicketId(qrText).trim() - if (!ticketId) return - - // Session-local de-dup. Distinct from the backend's "already - // registered" — this guards against the QR being held in front - // of the camera for multiple decode frames. - if (scanned.value.some(r => r.ticketId === ticketId)) { - lastScan.value = { status: 'duplicate-session', ticketId } - isPaused.value = true - return - } - - isProcessing.value = true - try { - const ticket = await transport.call>( - 'events_ticket_register', - { - event_id: activityId.value, - ticket_id: ticketId, - }, - ) - const name = (ticket?.name as string | null | undefined) ?? null - scanned.value = [ - { ticketId, name, registeredAt: new Date().toISOString() }, - ...scanned.value, - ] - lastScan.value = { status: 'ok', ticketId, ticket } - } catch (e) { - // Backend RPC errors arrive as NostrRpcError with the - // string in `.message`: "Ticket not paid for", "Ticket - // already registered", "Ticket does not exist on this - // event", "You do not own this event", etc. - lastScan.value = { - status: 'error', - ticketId, - message: e instanceof Error ? e.message : String(e), - } - } finally { - isProcessing.value = false - // Pause the decode loop regardless of outcome. The operator - // taps "Scan next" to resume; this guarantees they see the - // banner and can correct (let the attendee in, deny entry, - // etc.) before the next QR comes into frame. - isPaused.value = true - } - } - - function resume() { - lastScan.value = null - isPaused.value = false - } - - function clearScanned() { - scanned.value = [] - lastScan.value = null - isPaused.value = false - } - - return { - isProcessing, - isPaused, - lastScan, - scanned, - onDecode, - resume, - clearScanned, - } -} diff --git a/src/modules/activities/index.ts b/src/modules/activities/index.ts index 4d7bc9c..ef0b957 100644 --- a/src/modules/activities/index.ts +++ b/src/modules/activities/index.ts @@ -78,15 +78,6 @@ export const activitiesModule = createModulePlugin({ requiresAuth: true, }, }, - { - path: '/scan/:activityId', - name: 'scan-tickets', - component: () => import('./views/ScanTicketsPage.vue'), - meta: { - title: 'Scan Tickets', - requiresAuth: true, - }, - }, { path: '/events', name: 'events', diff --git a/src/modules/activities/views/ActivitiesPage.vue b/src/modules/activities/views/ActivitiesPage.vue index ccfc08e..d685a11 100644 --- a/src/modules/activities/views/ActivitiesPage.vue +++ b/src/modules/activities/views/ActivitiesPage.vue @@ -8,7 +8,7 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { SlidersHorizontal, ChevronDown, Ticket, Megaphone } from 'lucide-vue-next' +import { SlidersHorizontal, ChevronDown, Ticket } from 'lucide-vue-next' import { useActivities } from '../composables/useActivities' import { useAuth } from '@/composables/useAuthService' import ActivitySearchOverlay from '../components/ActivitySearchOverlay.vue' @@ -30,13 +30,11 @@ const { hasActiveFilters, selectedDate, onlyOwnedTickets, - onlyHosting, selectDate, setTemporal, toggleCategory, clearCategories, toggleOwnedTickets, - toggleHosting, resetFilters, subscribe, } = useActivities() @@ -81,10 +79,10 @@ function handleSelectActivity(activity: Activity) { - -
+ +
-
diff --git a/src/modules/activities/views/ActivityDetailPage.vue b/src/modules/activities/views/ActivityDetailPage.vue index fe9d322..84a0c64 100644 --- a/src/modules/activities/views/ActivityDetailPage.vue +++ b/src/modules/activities/views/ActivityDetailPage.vue @@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Separator } from '@/components/ui/separator' import { - Calendar, MapPin, ArrowLeft, Pencil, Ticket, CheckCircle2, ScanLine, + Calendar, MapPin, ArrowLeft, Pencil, Ticket, CheckCircle2, } from 'lucide-vue-next' import { useActivityDetail } from '../composables/useActivityDetail' import BookmarkButton from '../components/BookmarkButton.vue' @@ -64,10 +64,6 @@ function openEditDialog() { activitiesStore.showCreateDialog = true } -function openScannerPage() { - router.push({ name: 'scan-tickets', params: { activityId } }) -} - const dateDisplay = computed(() => { if (!activity.value) return '' const a = activity.value @@ -161,17 +157,6 @@ function goToMyTickets() { Back
-