diff --git a/src/modules/activities/components/CategorySelector.vue b/src/modules/activities/components/CategorySelector.vue new file mode 100644 index 0000000..5acb3f8 --- /dev/null +++ b/src/modules/activities/components/CategorySelector.vue @@ -0,0 +1,45 @@ + + + + + + {{ label(cat) }} + + + diff --git a/src/modules/activities/components/CreateActivityDialog.vue b/src/modules/activities/components/CreateActivityDialog.vue new file mode 100644 index 0000000..0c6935a --- /dev/null +++ b/src/modules/activities/components/CreateActivityDialog.vue @@ -0,0 +1,270 @@ + + + + + + + + + {{ t('activities.createNew') }} + + + Publish a new activity to Nostr relays + + + + + + + + Title * + + + + + + + + + + + Summary + + + + + + + + + + + Description * + + + + + + + + + + + + Start date * + + + + + + + + + + Start time * + + + + + + + + + + + + + End date + + + + + + + + + + End time + + + + + + + + + + + + + + Categories + + + + + + + Image URL + + + + + + + + + + ⚡ + {{ isPublishing ? 'Publishing...' : 'Publish Activity' }} + + + + + diff --git a/src/modules/activities/components/LocationPicker.vue b/src/modules/activities/components/LocationPicker.vue new file mode 100644 index 0000000..774a507 --- /dev/null +++ b/src/modules/activities/components/LocationPicker.vue @@ -0,0 +1,34 @@ + + + + + Location + + + + + + Enter the venue name and address + + + diff --git a/src/modules/activities/services/ActivitiesNostrService.ts b/src/modules/activities/services/ActivitiesNostrService.ts index 8f9008e..2215908 100644 --- a/src/modules/activities/services/ActivitiesNostrService.ts +++ b/src/modules/activities/services/ActivitiesNostrService.ts @@ -1,5 +1,5 @@ import { BaseService } from '@/core/base/BaseService' -import type { Event as NostrEvent } from 'nostr-tools' +import { finalizeEvent, type Event as NostrEvent, type EventTemplate } from 'nostr-tools' import type { SubscriptionConfig } from '@/modules/base/nostr/relay-hub' import { NIP52_KINDS, @@ -107,23 +107,28 @@ export class ActivitiesNostrService extends BaseService { /** * Publish a NIP-52 time-based calendar event. + * Requires an authenticated user with a signing key. */ async publishCalendarEvent( - eventData: Partial + eventData: Partial, + signingKeyHex: string ): Promise<{ success: number; total: number }> { if (!this.relayHub) { throw new Error('RelayHub not available') } const tags = buildCalendarTimeEventTags(eventData) - const eventTemplate = { + const template: EventTemplate = { kind: NIP52_KINDS.CALENDAR_TIME_EVENT, created_at: Math.floor(Date.now() / 1000), content: eventData.content ?? '', tags, } - return await this.relayHub.publishEvent(eventTemplate) + const privkeyBytes = hexToUint8Array(signingKeyHex) + const signedEvent = finalizeEvent(template, privkeyBytes) + + return await this.relayHub.publishEvent(signedEvent) } /** @@ -161,10 +166,17 @@ export class ActivitiesNostrService extends BaseService { } protected override async onDispose(): Promise { - // Clean up all active subscriptions for (const unsub of this.activeUnsubscribes) { unsub() } this.activeUnsubscribes = [] } } + +function hexToUint8Array(hex: string): Uint8Array { + const bytes = new Uint8Array(hex.length / 2) + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = parseInt(hex.substr(i, 2), 16) + } + return bytes +} diff --git a/src/modules/activities/views/ActivitiesPage.vue b/src/modules/activities/views/ActivitiesPage.vue index f07e21f..f5f41ca 100644 --- a/src/modules/activities/views/ActivitiesPage.vue +++ b/src/modules/activities/views/ActivitiesPage.vue @@ -10,8 +10,10 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { RefreshCw, Search, SlidersHorizontal, ChevronDown } from 'lucide-vue-next' +import { RefreshCw, Search, SlidersHorizontal, ChevronDown, Plus } from 'lucide-vue-next' +import { useAuth } from '@/composables/useAuthService' import { useActivities } from '../composables/useActivities' +import CreateActivityDialog from '../components/CreateActivityDialog.vue' import TemporalFilterBar from '../components/TemporalFilterBar.vue' import CategoryFilterBar from '../components/CategoryFilterBar.vue' import DatePickerStrip from '../components/DatePickerStrip.vue' @@ -20,6 +22,9 @@ import type { Activity } from '../types/activity' const router = useRouter() const { t } = useI18n() +const { isAuthenticated } = useAuth() + +const showCreateDialog = ref(false) const { activities, @@ -62,6 +67,14 @@ function handleRefresh() { + + + {{ t('activities.createNew') }} + Refresh @@ -156,5 +169,11 @@ function handleRefresh() { /> + + +
+ Enter the venue name and address +