-
Secondary text
-```
-
-**Semantic class mapping:**
-- Backgrounds: `bg-background`, `bg-card`, `bg-muted` (not `bg-white`, `bg-gray-100`)
-- Text: `text-foreground`, `text-muted-foreground` (not `text-gray-900`, `text-gray-600`)
-- Borders: `border-border`, `border-input` (not `border-gray-200`, `border-gray-300`)
-- Focus: `focus:ring-ring`, `focus:border-ring` (not `focus:ring-blue-500`)
-- Use opacity modifiers for subtle variations: `bg-primary/10`, `text-muted-foreground/70`
-
### **Code Conventions:**
- Use TypeScript interfaces over types for extendability
- Prefer functional and declarative patterns over classes (except for services)
@@ -794,19 +769,8 @@ quantity: productData.quantity ?? 1
- Electron Forge configured for cross-platform packaging
- TailwindCSS v4 integration via Vite plugin
-**Environment Variables** (see `.env.example`):
-- `VITE_APP_NAME` - Application display name
-- `VITE_NOSTR_RELAYS` - JSON array of Nostr relay WebSocket URLs
-- `VITE_ADMIN_PUBKEYS` - JSON array of admin public keys
-- `VITE_LNBITS_BASE_URL` - LNbits server URL for Lightning wallet
-- `VITE_API_KEY` - API key for LNbits authentication
-- `VITE_LNBITS_DEBUG` - Enable LNbits debug logging
-- `VITE_WEBSOCKET_ENABLED` - Enable real-time WebSocket balance updates
-- `VITE_LIGHTNING_DOMAIN` - Override domain for Lightning Addresses (optional, defaults to domain from `VITE_LNBITS_BASE_URL`)
-- `VITE_VAPID_PUBLIC_KEY` - VAPID key for push notifications
-- `VITE_PUSH_NOTIFICATIONS_ENABLED` - Enable push notifications
-- `VITE_PICTRS_BASE_URL` - pict-rs server URL for image uploads
-- `VITE_MARKET_NADDR` - Nostr address for market configuration
+**Environment:**
+- Nostr relay configuration via `VITE_NOSTR_RELAYS` environment variable
- PWA manifest configured for standalone app experience
- Service worker with automatic updates every hour
diff --git a/activities.html b/activities.html
deleted file mode 100644
index d227e52..0000000
--- a/activities.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
Sortir — Activités
-
-
-
-
-
-
-
-
diff --git a/package.json b/package.json
index ef5de0b..304528e 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,6 @@
"build": "vue-tsc -b && vite build",
"preview": "vite preview --host",
"analyze": "vite build --mode analyze",
- "dev:activities": "vite --host --config vite.activities.config.ts",
- "build:activities": "vue-tsc -b && vite build --config vite.activities.config.ts",
- "preview:activities": "vite preview --host --config vite.activities.config.ts",
"electron:dev": "concurrently \"vite --host\" \"electron-forge start\"",
"electron:build": "vue-tsc -b && vite build && electron-builder",
"electron:package": "electron-builder",
diff --git a/src/activities-app/App.vue b/src/activities-app/App.vue
deleted file mode 100644
index 4aaa827..0000000
--- a/src/activities-app/App.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/activities-app/app.config.ts b/src/activities-app/app.config.ts
deleted file mode 100644
index 30cbb22..0000000
--- a/src/activities-app/app.config.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { AppConfig } from '@/core/types'
-
-/**
- * Standalone activities app configuration.
- * Only enables base + activities modules.
- */
-export const appConfig: AppConfig = {
- modules: {
- base: {
- name: 'base',
- enabled: true,
- lazy: false,
- config: {
- nostr: {
- relays: JSON.parse(import.meta.env.VITE_NOSTR_RELAYS || '["wss://relay.damus.io", "wss://nos.lol"]')
- },
- auth: {
- sessionTimeout: 24 * 60 * 60 * 1000,
- },
- pwa: {
- autoPrompt: true
- },
- imageUpload: {
- baseUrl: import.meta.env.VITE_PICTRS_BASE_URL || 'https://img.mydomain.com',
- maxSizeMB: 10,
- acceptedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/gif']
- }
- }
- },
- activities: {
- name: 'activities',
- enabled: true,
- lazy: false,
- config: {
- apiConfig: {
- baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || 'http://localhost:5000',
- apiKey: import.meta.env.VITE_API_KEY || ''
- },
- defaultMapCenter: { lat: 42.9667, lng: 1.6000 }, // Ariège, France
- maxTicketsPerUser: 10,
- enableMap: true,
- enablePrivateEvents: false
- }
- },
- },
-
- features: {
- pwa: true,
- pushNotifications: true,
- electronApp: false,
- developmentMode: import.meta.env.DEV
- }
-}
-
-export default appConfig
diff --git a/src/activities-app/app.ts b/src/activities-app/app.ts
deleted file mode 100644
index e997b9b..0000000
--- a/src/activities-app/app.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { createApp } from 'vue'
-import { createRouter, createWebHistory } from 'vue-router'
-import { createPinia } from 'pinia'
-import { pluginManager } from '@/core/plugin-manager'
-import { eventBus } from '@/core/event-bus'
-import { container } from '@/core/di-container'
-
-import appConfig from './app.config'
-import baseModule from '@/modules/base'
-import activitiesModule from '@/modules/activities'
-
-import App from './App.vue'
-
-import '@/assets/index.css'
-import { i18n } from '@/i18n'
-
-/**
- * Initialize the standalone activities app
- */
-export async function createAppInstance() {
- console.log('🚀 Starting Sortir — Activities App...')
-
- const app = createApp(App)
-
- // Collect routes from enabled modules only
- const moduleRoutes = [
- ...baseModule.routes || [],
- ...activitiesModule.routes || [],
- ].filter(Boolean)
-
- const router = createRouter({
- history: createWebHistory(),
- routes: [
- // Activities page is the home page in standalone mode
- {
- path: '/',
- redirect: '/activities'
- },
- {
- path: '/login',
- name: 'login',
- component: () => import('@/pages/Login.vue'),
- meta: { requiresAuth: false }
- },
- ...moduleRoutes,
- // App-specific routes
- {
- path: '/settings',
- name: 'settings',
- component: () => import('./views/SettingsPage.vue'),
- meta: { requiresAuth: false }
- },
- ]
- })
-
- const pinia = createPinia()
-
- app.use(router)
- app.use(pinia)
- app.use(i18n)
-
- // Initialize plugin manager
- pluginManager.init(app, router)
-
- // Register modules
- const moduleRegistrations = []
-
- if (appConfig.modules.base.enabled) {
- moduleRegistrations.push(
- pluginManager.register(baseModule, appConfig.modules.base)
- )
- }
-
- if (appConfig.modules.activities?.enabled) {
- moduleRegistrations.push(
- pluginManager.register(activitiesModule, appConfig.modules.activities)
- )
- }
-
- await Promise.all(moduleRegistrations)
- await pluginManager.installAll()
-
- // Initialize auth
- const { auth } = await import('@/composables/useAuthService')
- await auth.initialize()
-
- // Auth guard — only redirect for routes that explicitly require auth
- router.beforeEach(async (to, _from, next) => {
- const requiresAuth = to.meta.requiresAuth === true
-
- if (requiresAuth && !auth.isAuthenticated.value) {
- next('/login')
- } else if (to.path === '/login' && auth.isAuthenticated.value) {
- next('/')
- } else {
- next()
- }
- })
-
- // Global error handling
- app.config.errorHandler = (err, _vm, info) => {
- console.error('Global error:', err, info)
- eventBus.emit('app:error', { error: err, info }, 'app')
- }
-
- if (appConfig.features.developmentMode) {
- ;(window as any).__pluginManager = pluginManager
- ;(window as any).__eventBus = eventBus
- ;(window as any).__container = container
- }
-
- console.log('✅ Sortir app initialized')
- return { app, router }
-}
-
-export async function startApp() {
- try {
- const { app } = await createAppInstance()
- app.mount('#app')
- console.log('🎉 Sortir app started!')
- eventBus.emit('app:started', {}, 'app')
- } catch (error) {
- console.error('💥 Failed to start Sortir app:', error)
- document.getElementById('app')!.innerHTML = `
-
-
Failed to Start
-
${error instanceof Error ? error.message : 'Unknown error'}
-
Please refresh the page.
-
- `
- }
-}
diff --git a/src/activities-app/main.ts b/src/activities-app/main.ts
deleted file mode 100644
index c9c8429..0000000
--- a/src/activities-app/main.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { startApp } from './app'
-import { registerSW } from 'virtual:pwa-register'
-import 'vue-sonner/style.css'
-
-// PWA service worker with periodic updates
-const intervalMS = 60 * 60 * 1000 // 1 hour
-registerSW({
- onRegistered(r) {
- r && setInterval(() => {
- r.update()
- }, intervalMS)
- },
- onOfflineReady() {
- console.log('Sortir app ready to work offline')
- }
-})
-
-startApp()
diff --git a/src/activities-app/views/SettingsPage.vue b/src/activities-app/views/SettingsPage.vue
deleted file mode 100644
index 007264c..0000000
--- a/src/activities-app/views/SettingsPage.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
Settings
-
-
-
-
Account
-
-
- {{ userPubkey }}
-
-
-
-
-
- Log in to bookmark activities, RSVP, and purchase tickets.
-
-
-
-
-
-
-
-
-
-
Appearance
-
- Theme
-
-
-
-
-
-
-
-
-
Language
-
-
-
-
-
-
diff --git a/src/app.config.ts b/src/app.config.ts
index 11e2df8..8060762 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -75,21 +75,6 @@ export const appConfig: AppConfig = {
maxTicketsPerUser: 10
}
},
- activities: {
- name: 'activities',
- enabled: true,
- lazy: false,
- config: {
- apiConfig: {
- baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || 'http://localhost:5000',
- apiKey: import.meta.env.VITE_API_KEY || ''
- },
- defaultMapCenter: { lat: 46.6034, lng: 1.8883 },
- maxTicketsPerUser: 10,
- enableMap: true,
- enablePrivateEvents: false
- }
- },
wallet: {
name: 'wallet',
enabled: true,
diff --git a/src/app.ts b/src/app.ts
index 1d11e26..459283e 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -16,7 +16,6 @@ import chatModule from './modules/chat'
import eventsModule from './modules/events'
import marketModule from './modules/market'
import walletModule from './modules/wallet'
-import activitiesModule from './modules/activities'
// Root component
import App from './App.vue'
@@ -44,8 +43,7 @@ export async function createAppInstance() {
...chatModule.routes || [],
...eventsModule.routes || [],
...marketModule.routes || [],
- ...walletModule.routes || [],
- ...activitiesModule.routes || []
+ ...walletModule.routes || []
].filter(Boolean)
// Create router with all routes available immediately
@@ -128,13 +126,6 @@ export async function createAppInstance() {
)
}
- // Register activities module (Nostr-native events)
- if (appConfig.modules.activities?.enabled) {
- moduleRegistrations.push(
- pluginManager.register(activitiesModule, appConfig.modules.activities)
- )
- }
-
// Wait for all modules to register
await Promise.all(moduleRegistrations)
diff --git a/src/composables/useModularNavigation.ts b/src/composables/useModularNavigation.ts
index c57c10c..af47f66 100644
--- a/src/composables/useModularNavigation.ts
+++ b/src/composables/useModularNavigation.ts
@@ -42,19 +42,11 @@ export function useModularNavigation() {
})
}
- if (appConfig.modules.activities?.enabled) {
- items.push({
- name: t('nav.activities'),
- href: '/activities',
- requiresAuth: false
- })
- }
-
if (appConfig.modules.chat.enabled) {
- items.push({
- name: t('nav.chat'),
- href: '/chat',
- requiresAuth: true
+ items.push({
+ name: t('nav.chat'),
+ href: '/chat',
+ requiresAuth: true
})
}
diff --git a/src/core/di-container.ts b/src/core/di-container.ts
index aa16b78..32221f5 100644
--- a/src/core/di-container.ts
+++ b/src/core/di-container.ts
@@ -142,10 +142,6 @@ export const SERVICE_TOKENS = {
// Events services
EVENTS_SERVICE: Symbol('eventsService'),
-
- // Activities services (Nostr-native events module)
- ACTIVITIES_NOSTR_SERVICE: Symbol('activitiesNostrService'),
- ACTIVITIES_TICKET_API: Symbol('activitiesTicketApi'),
// Invoice services
INVOICE_SERVICE: Symbol('invoiceService'),
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index 70fe924..7c39b78 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -9,7 +9,6 @@ const messages: LocaleMessages = {
events: 'Events',
market: 'Market',
chat: 'Chat',
- activities: 'Activities',
login: 'Login',
logout: 'Logout'
},
@@ -30,67 +29,6 @@ const messages: LocaleMessages = {
de: 'German',
zh: 'Chinese'
},
- activities: {
- title: 'Activities',
- createNew: 'Create Activity',
- noActivities: 'No activities found',
- filters: {
- all: 'All',
- today: 'Today',
- tomorrow: 'Tomorrow',
- thisWeek: 'This Week',
- thisMonth: 'This Month',
- },
- categories: {
- concert: 'Concert',
- workshop: 'Workshop',
- market: 'Market',
- festival: 'Festival',
- exhibition: 'Exhibition',
- sport: 'Sport',
- theater: 'Theater',
- cinema: 'Cinema',
- party: 'Party',
- talk: 'Talk',
- conference: 'Conference',
- meetup: 'Meetup',
- food: 'Food',
- outdoor: 'Outdoor',
- kids: 'Kids',
- wellness: 'Wellness',
- technology: 'Technology',
- art: 'Art',
- music: 'Music',
- dance: 'Dance',
- literature: 'Literature',
- comedy: 'Comedy',
- charity: 'Charity',
- tradition: 'Tradition',
- other: 'Other',
- },
- detail: {
- getTicket: 'Get Ticket',
- going: 'Going',
- maybe: 'Maybe',
- notGoing: 'Not Going',
- contactOrganizer: 'Contact Organizer',
- organizer: 'Organizer',
- location: 'Location',
- when: 'When',
- tickets: 'Tickets',
- ticketsAvailable: '{count} tickets available',
- soldOut: 'Sold Out',
- free: 'Free',
- },
- tickets: {
- myTickets: 'My Tickets',
- scanTicket: 'Scan Ticket',
- noTickets: 'No tickets yet',
- paid: 'Paid',
- pending: 'Pending',
- registered: 'Registered',
- },
- },
dateTimeFormats: {
short: {
year: 'numeric',
diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts
index d0fa65d..303ed46 100644
--- a/src/i18n/locales/es.ts
+++ b/src/i18n/locales/es.ts
@@ -9,7 +9,6 @@ const messages: LocaleMessages = {
events: 'Eventos',
market: 'Mercado',
chat: 'Chat',
- activities: 'Actividades',
login: 'Iniciar Sesión',
logout: 'Cerrar Sesión'
},
diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts
index e18f854..2fb2b3c 100644
--- a/src/i18n/locales/fr.ts
+++ b/src/i18n/locales/fr.ts
@@ -9,7 +9,6 @@ const messages: LocaleMessages = {
events: 'Événements',
market: 'Marché',
chat: 'Chat',
- activities: 'Activités',
login: 'Connexion',
logout: 'Déconnexion'
},
@@ -30,67 +29,6 @@ const messages: LocaleMessages = {
de: 'Allemand',
zh: 'Chinois'
},
- activities: {
- title: 'Activités',
- createNew: 'Créer une activité',
- noActivities: 'Aucune activité trouvée',
- filters: {
- all: 'Tout',
- today: "Aujourd'hui",
- tomorrow: 'Demain',
- thisWeek: 'Cette semaine',
- thisMonth: 'Ce mois-ci',
- },
- categories: {
- concert: 'Concert',
- workshop: 'Atelier',
- market: 'Marché',
- festival: 'Festival',
- exhibition: 'Exposition',
- sport: 'Sport',
- theater: 'Théâtre',
- cinema: 'Cinéma',
- party: 'Fête',
- talk: 'Conférence',
- conference: 'Congrès',
- meetup: 'Rencontre',
- food: 'Gastronomie',
- outdoor: 'Plein air',
- kids: 'Enfants',
- wellness: 'Bien-être',
- technology: 'Technologie',
- art: 'Art',
- music: 'Musique',
- dance: 'Danse',
- literature: 'Littérature',
- comedy: 'Humour',
- charity: 'Caritatif',
- tradition: 'Tradition',
- other: 'Autre',
- },
- detail: {
- getTicket: 'Obtenir un billet',
- going: 'Présent',
- maybe: 'Peut-être',
- notGoing: 'Absent',
- contactOrganizer: "Contacter l'organisateur",
- organizer: 'Organisateur',
- location: 'Lieu',
- when: 'Quand',
- tickets: 'Billets',
- ticketsAvailable: '{count} billets disponibles',
- soldOut: 'Épuisé',
- free: 'Gratuit',
- },
- tickets: {
- myTickets: 'Mes billets',
- scanTicket: 'Scanner le billet',
- noTickets: 'Pas encore de billets',
- paid: 'Payé',
- pending: 'En attente',
- registered: 'Enregistré',
- },
- },
dateTimeFormats: {
short: {
year: 'numeric',
diff --git a/src/i18n/types.ts b/src/i18n/types.ts
index 9faf7d7..12747de 100644
--- a/src/i18n/types.ts
+++ b/src/i18n/types.ts
@@ -7,7 +7,6 @@ export interface LocaleMessages {
events: string
market: string
chat: string
- activities: string
login: string
logout: string
}
@@ -30,42 +29,6 @@ export interface LocaleMessages {
de: string
zh: string
}
- // Activities module
- activities?: {
- title: string
- createNew: string
- noActivities: string
- filters: {
- all: string
- today: string
- tomorrow: string
- thisWeek: string
- thisMonth: string
- }
- categories: Record
- detail: {
- getTicket: string
- going: string
- maybe: string
- notGoing: string
- contactOrganizer: string
- organizer: string
- location: string
- when: string
- tickets: string
- ticketsAvailable: string
- soldOut: string
- free: string
- }
- tickets: {
- myTickets: string
- scanTicket: string
- noTickets: string
- paid: string
- pending: string
- registered: string
- }
- }
// Add date/time formats
dateTimeFormats: {
short: {
diff --git a/src/modules/activities/components/ActivityCard.vue b/src/modules/activities/components/ActivityCard.vue
deleted file mode 100644
index 6503536..0000000
--- a/src/modules/activities/components/ActivityCard.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
-
-
![]()
-
-
-
-
-
-
- {{ categoryLabel }}
-
-
-
-
- {{ priceDisplay }}
-
-
-
-
-
-
- {{ activity.title }}
-
-
-
-
- {{ activity.summary }}
-
-
-
-
-
-
- {{ dateDisplay }}
-
-
-
-
-
- {{ activity.location }}
-
-
-
-
-
-
- {{ t('activities.detail.ticketsAvailable', { count: activity.ticketInfo.available }) }}
-
-
- {{ t('activities.detail.soldOut') }}
-
-
-
-
-
-
diff --git a/src/modules/activities/components/ActivityList.vue b/src/modules/activities/components/ActivityList.vue
deleted file mode 100644
index 4dac127..0000000
--- a/src/modules/activities/components/ActivityList.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {{ t('activities.noActivities') }}
-
-
- Try adjusting your filters or check back later
-
-
-
-
-
-
diff --git a/src/modules/activities/components/CategoryFilterBar.vue b/src/modules/activities/components/CategoryFilterBar.vue
deleted file mode 100644
index 8069dcf..0000000
--- a/src/modules/activities/components/CategoryFilterBar.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
- Categories
-
-
-
-
- {{ categoryLabel(cat) }}
-
-
-
-
diff --git a/src/modules/activities/components/DatePickerStrip.vue b/src/modules/activities/components/DatePickerStrip.vue
deleted file mode 100644
index b9ea5cb..0000000
--- a/src/modules/activities/components/DatePickerStrip.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/modules/activities/components/TemporalFilterBar.vue b/src/modules/activities/components/TemporalFilterBar.vue
deleted file mode 100644
index 2ccb4ec..0000000
--- a/src/modules/activities/components/TemporalFilterBar.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/modules/activities/composables/useActivities.ts b/src/modules/activities/composables/useActivities.ts
deleted file mode 100644
index 7d4186b..0000000
--- a/src/modules/activities/composables/useActivities.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { ref, computed, onUnmounted } from 'vue'
-import { tryInjectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ActivitiesNostrService } from '../services/ActivitiesNostrService'
-import type { CalendarEventFilters } from '../services/ActivitiesNostrService'
-import { useActivitiesStore } from '../stores/activities'
-import { useActivityFilters } from './useActivityFilters'
-
-/**
- * Main composable for activities discovery.
- * Subscribes to NIP-52 events via ActivitiesNostrService and manages the activity feed.
- */
-export function useActivities() {
- const store = useActivitiesStore()
- const filters = useActivityFilters()
-
- const isSubscribed = ref(false)
- const subscriptionError = ref(null)
- let unsubscribe: (() => void) | null = null
-
- // Filtered and sorted activities
- const filteredActivities = computed(() => {
- const upcoming = store.upcomingActivities
- return filters.applyFilters(upcoming)
- })
-
- const pastFilteredActivities = computed(() => {
- return filters.applyFilters(store.pastActivities)
- })
-
- /**
- * Subscribe to NIP-52 calendar events from Nostr relays.
- */
- function subscribe(eventFilters?: CalendarEventFilters) {
- if (isSubscribed.value) return
-
- const nostrService = tryInjectService(SERVICE_TOKENS.ACTIVITIES_NOSTR_SERVICE)
- if (!nostrService) {
- subscriptionError.value = 'Activities service not available'
- return
- }
-
- try {
- store.isLoading = true
- subscriptionError.value = null
-
- unsubscribe = nostrService.subscribeToCalendarEvents(
- (activity) => {
- store.upsertActivity(activity)
- store.isLoading = false
- },
- eventFilters
- )
-
- isSubscribed.value = true
-
- // Set loading to false after a timeout (in case no events arrive)
- setTimeout(() => {
- store.isLoading = false
- }, 5000)
- } catch (err) {
- subscriptionError.value = err instanceof Error ? err.message : 'Failed to subscribe'
- store.isLoading = false
- }
- }
-
- /**
- * One-shot query for calendar events.
- */
- async function query(eventFilters?: CalendarEventFilters) {
- const nostrService = tryInjectService(SERVICE_TOKENS.ACTIVITIES_NOSTR_SERVICE)
- if (!nostrService) {
- subscriptionError.value = 'Activities service not available'
- return
- }
-
- try {
- store.isLoading = true
- subscriptionError.value = null
- const activities = await nostrService.queryCalendarEvents(eventFilters)
- store.upsertActivities(activities)
- } catch (err) {
- subscriptionError.value = err instanceof Error ? err.message : 'Failed to query activities'
- } finally {
- store.isLoading = false
- }
- }
-
- /**
- * Unsubscribe from relay events.
- */
- function stop() {
- if (unsubscribe) {
- unsubscribe()
- unsubscribe = null
- }
- isSubscribed.value = false
- }
-
- /**
- * Refresh: stop current subscription and re-subscribe.
- */
- function refresh(eventFilters?: CalendarEventFilters) {
- stop()
- store.clearAll()
- subscribe(eventFilters)
- }
-
- // Cleanup on unmount
- onUnmounted(() => {
- stop()
- })
-
- return {
- // State
- activities: filteredActivities,
- pastActivities: pastFilteredActivities,
- allActivities: computed(() => store.activities),
- isLoading: computed(() => store.isLoading),
- isSubscribed,
- error: subscriptionError,
- lastUpdated: computed(() => store.lastUpdated),
-
- // Filter controls (re-exported)
- ...filters,
-
- // Actions
- subscribe,
- query,
- stop,
- refresh,
- }
-}
diff --git a/src/modules/activities/composables/useActivityDetail.ts b/src/modules/activities/composables/useActivityDetail.ts
deleted file mode 100644
index e29a272..0000000
--- a/src/modules/activities/composables/useActivityDetail.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { ref, computed, onMounted, onUnmounted } from 'vue'
-import { tryInjectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ActivitiesNostrService } from '../services/ActivitiesNostrService'
-import { useActivitiesStore } from '../stores/activities'
-import type { Activity } from '../types/activity'
-
-/**
- * Composable for loading a single activity by its d-tag identifier.
- * First checks the store cache, then queries relays if not found.
- */
-export function useActivityDetail(activityId: string) {
- const store = useActivitiesStore()
- const isLoading = ref(false)
- const error = ref(null)
- let unsubscribe: (() => void) | null = null
-
- const activity = computed(() =>
- store.getActivityById(activityId)
- )
-
- async function load() {
- // Already in cache
- if (activity.value) return
-
- const nostrService = tryInjectService(SERVICE_TOKENS.ACTIVITIES_NOSTR_SERVICE)
- if (!nostrService) {
- error.value = 'Activities service not available'
- return
- }
-
- try {
- isLoading.value = true
- error.value = null
-
- // Subscribe and wait for this specific event
- unsubscribe = nostrService.subscribeToCalendarEvents(
- (incoming) => {
- store.upsertActivity(incoming)
- if (incoming.id === activityId) {
- isLoading.value = false
- }
- }
- )
-
- // Also do a one-shot query
- const results = await nostrService.queryCalendarEvents()
- store.upsertActivities(results)
-
- // If we still don't have it after query, stop loading
- setTimeout(() => {
- isLoading.value = false
- if (!activity.value) {
- error.value = 'Activity not found'
- }
- }, 5000)
- } catch (err) {
- error.value = err instanceof Error ? err.message : 'Failed to load activity'
- isLoading.value = false
- }
- }
-
- onMounted(() => {
- load()
- })
-
- onUnmounted(() => {
- if (unsubscribe) {
- unsubscribe()
- }
- })
-
- return {
- activity,
- isLoading,
- error,
- reload: load,
- }
-}
diff --git a/src/modules/activities/composables/useActivityFilters.ts b/src/modules/activities/composables/useActivityFilters.ts
deleted file mode 100644
index a42016c..0000000
--- a/src/modules/activities/composables/useActivityFilters.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { ref, computed } from 'vue'
-import {
- startOfDay, endOfDay, startOfWeek, endOfWeek,
- startOfMonth, endOfMonth, addDays,
-} from 'date-fns'
-import type { Activity } from '../types/activity'
-import type { ActivityCategory } from '../types/category'
-import type { TemporalFilter, ActivityFilters } from '../types/filters'
-import { DEFAULT_FILTERS } from '../types/filters'
-
-/**
- * Composable for managing activity filter state and applying filters reactively.
- */
-export function useActivityFilters() {
- const temporal = ref(DEFAULT_FILTERS.temporal)
- const selectedCategories = ref([])
- const searchQuery = ref('')
-
- const filters = computed(() => ({
- temporal: temporal.value,
- categories: selectedCategories.value,
- search: searchQuery.value || undefined,
- }))
-
- /**
- * Apply the current filters to a list of activities.
- */
- function applyFilters(activities: Activity[]): Activity[] {
- let result = activities
-
- // Temporal filter
- result = applyTemporalFilter(result, temporal.value)
-
- // Category filter
- if (selectedCategories.value.length > 0) {
- result = result.filter(a =>
- a.category && selectedCategories.value.includes(a.category)
- )
- }
-
- // Search filter
- if (searchQuery.value.trim()) {
- const query = searchQuery.value.toLowerCase().trim()
- result = result.filter(a =>
- a.title.toLowerCase().includes(query) ||
- a.summary?.toLowerCase().includes(query) ||
- a.description.toLowerCase().includes(query) ||
- a.location?.toLowerCase().includes(query)
- )
- }
-
- return result
- }
-
- function setTemporal(value: TemporalFilter) {
- temporal.value = value
- }
-
- function toggleCategory(category: ActivityCategory) {
- const idx = selectedCategories.value.indexOf(category)
- if (idx >= 0) {
- selectedCategories.value.splice(idx, 1)
- } else {
- selectedCategories.value.push(category)
- }
- }
-
- function clearCategories() {
- selectedCategories.value = []
- }
-
- function resetFilters() {
- temporal.value = DEFAULT_FILTERS.temporal
- selectedCategories.value = []
- searchQuery.value = ''
- }
-
- const hasActiveFilters = computed(() =>
- temporal.value !== 'all' ||
- selectedCategories.value.length > 0 ||
- searchQuery.value.trim().length > 0
- )
-
- return {
- // State
- temporal,
- selectedCategories,
- searchQuery,
- filters,
- hasActiveFilters,
-
- // Actions
- applyFilters,
- setTemporal,
- toggleCategory,
- clearCategories,
- resetFilters,
- }
-}
-
-// --- Helpers ---
-
-function applyTemporalFilter(activities: Activity[], filter: TemporalFilter): Activity[] {
- if (filter === 'all') return activities
-
- const now = new Date()
- let start: Date
- let end: Date
-
- switch (filter) {
- case 'today':
- start = startOfDay(now)
- end = endOfDay(now)
- break
- case 'tomorrow':
- start = startOfDay(addDays(now, 1))
- end = endOfDay(addDays(now, 1))
- break
- case 'this-week':
- start = startOfWeek(now, { weekStartsOn: 1 })
- end = endOfWeek(now, { weekStartsOn: 1 })
- break
- case 'this-month':
- start = startOfMonth(now)
- end = endOfMonth(now)
- break
- default:
- return activities
- }
-
- return activities.filter(a => {
- const activityEnd = a.endDate ?? a.startDate
- // Activity overlaps with the filter range
- return a.startDate <= end && activityEnd >= start
- })
-}
diff --git a/src/modules/activities/index.ts b/src/modules/activities/index.ts
deleted file mode 100644
index 2816a86..0000000
--- a/src/modules/activities/index.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { createModulePlugin } from '@/core/base/BaseModulePlugin'
-import { SERVICE_TOKENS } from '@/core/di-container'
-import { ActivitiesNostrService } from './services/ActivitiesNostrService'
-import { TicketApiService, type TicketApiConfig } from './services/TicketApiService'
-
-export interface ActivitiesModuleConfig {
- apiConfig: TicketApiConfig
- defaultMapCenter?: { lat: number; lng: number }
- maxTicketsPerUser?: number
- enableMap?: boolean
- enablePrivateEvents?: boolean
-}
-
-/**
- * Activities Module Plugin
- *
- * Nostr-native communal events module using NIP-52 Calendar Events
- * for discovery, with database-backed ticketing via LNbits.
- */
-export const activitiesModule = createModulePlugin({
- name: 'activities',
- version: '1.0.0',
- dependencies: ['base'],
-
- routes: [
- {
- path: '/activities',
- name: 'activities',
- component: () => import('./views/ActivitiesPage.vue'),
- meta: {
- title: 'Activities',
- requiresAuth: false,
- },
- },
- {
- path: '/activities/calendar',
- name: 'activities-calendar',
- component: () => import('./views/ActivitiesCalendarPage.vue'),
- meta: {
- title: 'Calendar',
- requiresAuth: false,
- },
- },
- {
- path: '/activities/map',
- name: 'activities-map',
- component: () => import('./views/ActivitiesMapPage.vue'),
- meta: {
- title: 'Map',
- requiresAuth: false,
- },
- },
- {
- path: '/activities/favorites',
- name: 'activities-favorites',
- component: () => import('./views/ActivitiesFavoritesPage.vue'),
- meta: {
- title: 'Favorites',
- requiresAuth: true,
- },
- },
- {
- path: '/activities/:id',
- name: 'activity-detail',
- component: () => import('./views/ActivityDetailPage.vue'),
- meta: {
- title: 'Activity',
- requiresAuth: false,
- },
- },
- {
- path: '/my-tickets',
- name: 'my-tickets-v2',
- component: () => import('./views/MyTicketsPage.vue'),
- meta: {
- title: 'My Tickets',
- requiresAuth: true,
- },
- },
- ],
-
- eventListeners: [
- {
- event: 'payment:completed',
- handler: (event) => {
- console.log('Activities module: payment completed', event.data)
- },
- description: 'Handle payment completion for ticket purchases',
- },
- ],
-
- onInstall: async (_app, options) => {
- const config = options?.config as ActivitiesModuleConfig | undefined
- if (!config) {
- throw new Error('Activities module requires configuration')
- }
-
- const { container } = await import('@/core/di-container')
-
- // 1. Create services
- const nostrService = new ActivitiesNostrService()
- const ticketApi = new TicketApiService(config.apiConfig)
-
- // 2. Register in DI container BEFORE initialization
- container.provide(SERVICE_TOKENS.ACTIVITIES_NOSTR_SERVICE, nostrService)
- container.provide(SERVICE_TOKENS.ACTIVITIES_TICKET_API, ticketApi)
-
- // 3. Initialize the Nostr service (needs RelayHub dependency)
- await nostrService.initialize({
- waitForDependencies: true,
- maxRetries: 3,
- })
- },
-
- onUninstall: async () => {
- const { container } = await import('@/core/di-container')
- container.remove(SERVICE_TOKENS.ACTIVITIES_NOSTR_SERVICE)
- container.remove(SERVICE_TOKENS.ACTIVITIES_TICKET_API)
- },
-})
-
-export default activitiesModule
-
-// Re-export types for external use
-export type { Activity, OrganizerInfo, ActivityTicketInfo } from './types/activity'
-export type { ActivityTicket, TicketStatus } from './types/ticket'
-export type { ActivityCategory } from './types/category'
-export type { CalendarTimeEvent, CalendarDateEvent, CalendarRSVP } from './types/nip52'
diff --git a/src/modules/activities/services/ActivitiesNostrService.ts b/src/modules/activities/services/ActivitiesNostrService.ts
deleted file mode 100644
index 8f9008e..0000000
--- a/src/modules/activities/services/ActivitiesNostrService.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { BaseService } from '@/core/base/BaseService'
-import type { Event as NostrEvent } from 'nostr-tools'
-import type { SubscriptionConfig } from '@/modules/base/nostr/relay-hub'
-import {
- NIP52_KINDS,
- parseCalendarTimeEvent,
- parseCalendarDateEvent,
- buildCalendarTimeEventTags,
- type CalendarTimeEvent,
-} from '../types/nip52'
-import {
- calendarTimeEventToActivity,
- calendarDateEventToActivity,
- type Activity,
-} from '../types/activity'
-
-export interface CalendarEventFilters {
- /** Only return events created after this timestamp */
- since?: number
- /** Only return events created before this timestamp */
- until?: number
- /** Filter by specific authors (pubkeys) */
- authors?: string[]
- /** Filter by hashtags (NIP-52 't' tags) */
- hashtags?: string[]
- /** Filter by geohash prefix (NIP-52 'g' tag) */
- geohash?: string
-}
-
-/**
- * Service for subscribing to and publishing NIP-52 Calendar Events via RelayHub.
- * Extends BaseService for standardized dependency injection and lifecycle.
- */
-export class ActivitiesNostrService extends BaseService {
- protected readonly metadata = {
- name: 'ActivitiesNostrService',
- version: '1.0.0',
- dependencies: ['RelayHub'],
- }
-
- private activeUnsubscribes: Array<() => void> = []
-
- protected async onInitialize(): Promise {
- this.debug('ActivitiesNostrService initialized')
- }
-
- /**
- * Subscribe to NIP-52 calendar events from relays.
- * Returns an unsubscribe function.
- */
- subscribeToCalendarEvents(
- onActivity: (activity: Activity) => void,
- filters?: CalendarEventFilters
- ): () => void {
- if (!this.relayHub) {
- throw new Error('RelayHub not available')
- }
-
- const nostrFilters = this.buildNostrFilters(filters)
-
- const subscriptionId = `activities-calendar-${Date.now()}`
-
- const config: SubscriptionConfig = {
- id: subscriptionId,
- filters: nostrFilters,
- onEvent: (event: NostrEvent) => {
- const activity = this.parseNostrEventToActivity(event)
- if (activity) {
- onActivity(activity)
- }
- },
- onEose: () => {
- this.debug('End of stored events for subscription', subscriptionId)
- },
- }
-
- const unsubscribe = this.relayHub.subscribe(config)
- this.activeUnsubscribes.push(unsubscribe)
-
- return () => {
- unsubscribe()
- this.activeUnsubscribes = this.activeUnsubscribes.filter(fn => fn !== unsubscribe)
- }
- }
-
- /**
- * Query relays for calendar events (one-shot, not a subscription).
- */
- async queryCalendarEvents(filters?: CalendarEventFilters): Promise {
- if (!this.relayHub) {
- throw new Error('RelayHub not available')
- }
-
- const nostrFilters = this.buildNostrFilters(filters)
- const events: NostrEvent[] = await this.relayHub.queryEvents(nostrFilters)
-
- const activities: Activity[] = []
- for (const event of events) {
- const activity = this.parseNostrEventToActivity(event)
- if (activity) {
- activities.push(activity)
- }
- }
-
- return activities
- }
-
- /**
- * Publish a NIP-52 time-based calendar event.
- */
- async publishCalendarEvent(
- eventData: Partial
- ): Promise<{ success: number; total: number }> {
- if (!this.relayHub) {
- throw new Error('RelayHub not available')
- }
-
- const tags = buildCalendarTimeEventTags(eventData)
- const eventTemplate = {
- kind: NIP52_KINDS.CALENDAR_TIME_EVENT,
- created_at: Math.floor(Date.now() / 1000),
- content: eventData.content ?? '',
- tags,
- }
-
- return await this.relayHub.publishEvent(eventTemplate)
- }
-
- /**
- * Parse a raw Nostr event into an Activity view model.
- */
- private parseNostrEventToActivity(event: NostrEvent): Activity | null {
- if (event.kind === NIP52_KINDS.CALENDAR_TIME_EVENT) {
- const parsed = parseCalendarTimeEvent(event)
- if (parsed) return calendarTimeEventToActivity(parsed)
- }
-
- if (event.kind === NIP52_KINDS.CALENDAR_DATE_EVENT) {
- const parsed = parseCalendarDateEvent(event)
- if (parsed) return calendarDateEventToActivity(parsed)
- }
-
- return null
- }
-
- /**
- * Build nostr-tools Filter objects from our CalendarEventFilters.
- */
- private buildNostrFilters(filters?: CalendarEventFilters): Array> {
- const filter: Record = {
- kinds: [NIP52_KINDS.CALENDAR_DATE_EVENT, NIP52_KINDS.CALENDAR_TIME_EVENT],
- }
-
- if (filters?.since) filter.since = filters.since
- if (filters?.until) filter.until = filters.until
- if (filters?.authors?.length) filter.authors = filters.authors
- if (filters?.hashtags?.length) filter['#t'] = filters.hashtags
- if (filters?.geohash) filter['#g'] = [filters.geohash]
-
- return [filter]
- }
-
- protected override async onDispose(): Promise {
- // Clean up all active subscriptions
- for (const unsub of this.activeUnsubscribes) {
- unsub()
- }
- this.activeUnsubscribes = []
- }
-}
diff --git a/src/modules/activities/services/LnbitsPaymentProvider.ts b/src/modules/activities/services/LnbitsPaymentProvider.ts
deleted file mode 100644
index 140bad4..0000000
--- a/src/modules/activities/services/LnbitsPaymentProvider.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import type {
- PaymentProvider,
- CreateInvoiceParams,
- InvoiceResult,
- PaymentStatus,
- PayInvoiceResult,
-} from './PaymentProviderInterface'
-
-export interface LnbitsPaymentConfig {
- baseUrl: string
- apiKey: string
-}
-
-/**
- * LNbits implementation of PaymentProvider.
- * Talks to the LNbits REST API for invoice creation, payment, and status checks.
- */
-export class LnbitsPaymentProvider implements PaymentProvider {
- constructor(private config: LnbitsPaymentConfig) {}
-
- async createInvoice(params: CreateInvoiceParams): Promise {
- const response = await fetch(`${this.config.baseUrl}/api/v1/payments`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': this.config.apiKey,
- },
- body: JSON.stringify({
- out: false,
- amount: params.amount,
- memo: params.memo,
- extra: params.metadata,
- }),
- })
-
- if (!response.ok) {
- const error = await response.json().catch(() => ({ detail: 'Failed to create invoice' }))
- throw new Error(typeof error.detail === 'string' ? error.detail : 'Failed to create invoice')
- }
-
- const data = await response.json()
- return {
- paymentHash: data.payment_hash,
- paymentRequest: data.payment_request,
- }
- }
-
- async checkPaymentStatus(paymentHash: string): Promise {
- const response = await fetch(`${this.config.baseUrl}/api/v1/payments/${paymentHash}`, {
- headers: {
- 'X-Api-Key': this.config.apiKey,
- },
- })
-
- if (!response.ok) {
- throw new Error('Failed to check payment status')
- }
-
- const data = await response.json()
- return {
- paid: data.paid === true,
- preimage: data.preimage,
- }
- }
-
- async payInvoice(paymentRequest: string): Promise {
- const response = await fetch(`${this.config.baseUrl}/api/v1/payments`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Api-Key': this.config.apiKey,
- },
- body: JSON.stringify({
- out: true,
- bolt11: paymentRequest,
- }),
- })
-
- if (!response.ok) {
- const error = await response.json().catch(() => ({ detail: 'Failed to pay invoice' }))
- throw new Error(typeof error.detail === 'string' ? error.detail : 'Failed to pay invoice')
- }
-
- const data = await response.json()
- return {
- paymentHash: data.payment_hash,
- feeMsat: data.fee_msat ?? 0,
- preimage: data.preimage ?? '',
- }
- }
-}
diff --git a/src/modules/activities/services/PaymentProviderInterface.ts b/src/modules/activities/services/PaymentProviderInterface.ts
deleted file mode 100644
index e35fda0..0000000
--- a/src/modules/activities/services/PaymentProviderInterface.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Payment Provider Abstraction
- *
- * Enables swapping between LNbits and lightning.pub (or other providers)
- * without changing consuming code.
- */
-
-export interface CreateInvoiceParams {
- /** Amount in the specified currency */
- amount: number
- /** Currency code (e.g., 'sats', 'EUR') */
- currency: string
- /** Invoice memo/description */
- memo: string
- /** Arbitrary metadata attached to the invoice */
- metadata?: Record
-}
-
-export interface InvoiceResult {
- paymentHash: string
- paymentRequest: string
-}
-
-export interface PaymentStatus {
- paid: boolean
- preimage?: string
-}
-
-export interface PayInvoiceResult {
- paymentHash: string
- feeMsat: number
- preimage: string
-}
-
-/**
- * Abstract payment provider interface.
- * Implementations handle the specifics of LNbits, lightning.pub, etc.
- */
-export interface PaymentProvider {
- /** Create a Lightning invoice for receiving payment */
- createInvoice(params: CreateInvoiceParams): Promise
-
- /** Check whether an invoice has been paid */
- checkPaymentStatus(paymentHash: string): Promise
-
- /** Pay a Lightning invoice from the user's wallet */
- payInvoice(paymentRequest: string): Promise
-}
diff --git a/src/modules/activities/services/TicketApiService.ts b/src/modules/activities/services/TicketApiService.ts
deleted file mode 100644
index 61d5572..0000000
--- a/src/modules/activities/services/TicketApiService.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import type {
- ActivityTicket,
- TicketPurchaseInvoice,
- TicketPaymentStatus,
-} from '../types/ticket'
-
-export interface TicketApiConfig {
- baseUrl: string
- apiKey: string
-}
-
-/**
- * Database-backed ticketing API service.
- * Talks to the LNbits events extension for ticket inventory,
- * purchases, payment status, and validation.
- *
- * This is NOT a BaseService -- it's a simple API wrapper instantiated
- * with config at module install time (same pattern as EventsApiService).
- */
-export class TicketApiService {
- constructor(private config: TicketApiConfig) {}
-
- /**
- * Fetch all public events from the LNbits events extension.
- * Used to correlate Nostr activities with ticketed events.
- */
- async fetchTicketedEvents(): Promise {
- const response = await this.request(
- '/events/api/v1/events/public',
- { method: 'GET' }
- )
- return response
- }
-
- /**
- * Request a ticket purchase (creates a Lightning invoice).
- */
- async requestTicket(
- eventId: string,
- userId: string,
- accessToken: string
- ): Promise {
- const data = await this.request(
- `/events/api/v1/tickets/${eventId}/user/${userId}`,
- {
- method: 'GET',
- headers: {
- 'Authorization': `Bearer ${accessToken}`,
- },
- }
- )
-
- return {
- paymentHash: data.payment_hash,
- paymentRequest: data.payment_request,
- }
- }
-
- /**
- * Check whether a ticket payment has been confirmed.
- */
- async checkPaymentStatus(
- eventId: string,
- paymentHash: string
- ): Promise {
- const data = await this.request(
- `/events/api/v1/tickets/${eventId}/${paymentHash}`,
- { method: 'POST' }
- )
-
- return {
- paid: data.paid === true,
- ticketId: data.ticket_id,
- }
- }
-
- /**
- * Fetch all tickets for a user.
- */
- async fetchUserTickets(
- userId: string,
- accessToken: string
- ): Promise {
- const data = await this.request(
- `/events/api/v1/tickets/user/${userId}`,
- {
- method: 'GET',
- headers: {
- 'Authorization': `Bearer ${accessToken}`,
- },
- }
- )
-
- return (data as any[]).map(t => ({
- id: t.id,
- wallet: t.wallet,
- activityId: t.event,
- name: t.name,
- email: t.email,
- userId: t.user_id,
- registered: t.registered,
- paid: t.paid,
- time: t.time,
- regTimestamp: t.reg_timestamp,
- }))
- }
-
- /**
- * Validate/register a ticket at the door (scan).
- */
- async validateTicket(ticketId: string): Promise {
- const data = await this.request(
- `/events/api/v1/register/ticket/${ticketId}`,
- { method: 'GET' }
- )
-
- return (data as any[]).map(t => ({
- id: t.id,
- wallet: t.wallet,
- activityId: t.event,
- name: t.name,
- email: t.email,
- userId: t.user_id,
- registered: t.registered,
- paid: t.paid,
- time: t.time,
- regTimestamp: t.reg_timestamp,
- }))
- }
-
- /**
- * Internal fetch helper with standard headers and error handling.
- */
- private async request(path: string, init: RequestInit = {}): Promise {
- const headers: Record = {
- 'accept': 'application/json',
- 'X-API-KEY': this.config.apiKey,
- ...(init.headers as Record ?? {}),
- }
-
- const response = await fetch(`${this.config.baseUrl}${path}`, {
- ...init,
- headers,
- })
-
- if (!response.ok) {
- const error = await response.json().catch(() => ({ detail: `Request failed: ${path}` }))
- const errorMessage = typeof error.detail === 'string'
- ? error.detail
- : Array.isArray(error.detail)
- ? error.detail[0]?.msg ?? 'Request failed'
- : 'Request failed'
- throw new Error(errorMessage)
- }
-
- return response.json()
- }
-}
diff --git a/src/modules/activities/stores/activities.ts b/src/modules/activities/stores/activities.ts
deleted file mode 100644
index 1faec7b..0000000
--- a/src/modules/activities/stores/activities.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { defineStore } from 'pinia'
-import { ref, computed } from 'vue'
-import type { Activity } from '../types/activity'
-
-/**
- * Pinia store for cached activities from Nostr relays.
- * Handles deduplication by NIP-52 addressable event key (kind:pubkey:d-tag).
- */
-export const useActivitiesStore = defineStore('activities', () => {
- // State
- const activitiesMap = ref