diff --git a/src/app.config.ts b/src/app.config.ts
index e461872..8893996 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -64,7 +64,7 @@ export const appConfig: AppConfig = {
},
events: {
name: 'events',
- enabled: false,
+ enabled: true,
lazy: false,
config: {
apiConfig: {
diff --git a/src/modules/base/nostr/relay-hub.ts b/src/modules/base/nostr/relay-hub.ts
index 7cdd00f..486d3f2 100644
--- a/src/modules/base/nostr/relay-hub.ts
+++ b/src/modules/base/nostr/relay-hub.ts
@@ -540,12 +540,8 @@ export class RelayHub extends BaseService {
const successful = results.filter(result => result.status === 'fulfilled').length
const total = results.length
- this.emit('eventPublished', { eventId: event.id, success: successful, total })
- // Throw error if no relays accepted the event
- if (successful === 0) {
- throw new Error(`Failed to publish event - none of the ${total} relay(s) accepted it`)
- }
+ this.emit('eventPublished', { eventId: event.id, success: successful, total })
return { success: successful, total }
}
diff --git a/src/modules/nostr-feed/components/NostrFeed.vue b/src/modules/nostr-feed/components/NostrFeed.vue
index 529b7d7..2d0aa4c 100644
--- a/src/modules/nostr-feed/components/NostrFeed.vue
+++ b/src/modules/nostr-feed/components/NostrFeed.vue
@@ -99,22 +99,12 @@ const { getDisplayName, fetchProfiles } = useProfiles()
const { getEventReactions, subscribeToReactions, toggleLike } = useReactions()
// Use scheduled events service
-const {
- getEventsForSpecificDate,
- getCompletion,
- getTaskStatus,
- claimTask,
- startTask,
- completeEvent,
- unclaimTask,
- deleteTask,
- allCompletions
-} = useScheduledEvents()
+const { getEventsForSpecificDate, getCompletion, toggleComplete, allCompletions } = useScheduledEvents()
-// Selected date for viewing scheduled tasks (defaults to today)
+// Selected date for viewing events (defaults to today)
const selectedDate = ref(new Date().toISOString().split('T')[0])
-// Get scheduled tasks for the selected date (reactive)
+// Get scheduled events for the selected date (reactive)
const scheduledEventsForDate = computed(() => getEventsForSpecificDate(selectedDate.value))
// Navigate to previous day
@@ -153,20 +143,20 @@ const dateDisplayText = computed(() => {
const tomorrowStr = tomorrow.toISOString().split('T')[0]
if (selectedDate.value === today) {
- return "Today's Tasks"
+ return "Today's Events"
} else if (selectedDate.value === yesterdayStr) {
- return "Yesterday's Tasks"
+ return "Yesterday's Events"
} else if (selectedDate.value === tomorrowStr) {
- return "Tomorrow's Tasks"
+ return "Tomorrow's Events"
} else {
- // Format as "Tasks for Mon, Jan 15"
+ // Format as "Events for Mon, Jan 15"
const date = new Date(selectedDate.value + 'T00:00:00')
const formatted = date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
})
- return `Tasks for ${formatted}`
+ return `Events for ${formatted}`
}
})
@@ -265,49 +255,14 @@ async function onToggleLike(note: FeedPost) {
}
}
-// Task action handlers
-async function onClaimTask(event: ScheduledEvent, occurrence?: string) {
- console.log('👋 NostrFeed: Claiming task:', event.title)
+// Handle scheduled event completion toggle
+async function onToggleComplete(event: ScheduledEvent, occurrence?: string) {
+ console.log('🎯 NostrFeed: onToggleComplete called for event:', event.title, 'occurrence:', occurrence)
try {
- await claimTask(event, '', occurrence)
+ await toggleComplete(event, occurrence)
+ console.log('✅ NostrFeed: toggleComplete succeeded')
} catch (error) {
- console.error('❌ Failed to claim task:', error)
- }
-}
-
-async function onStartTask(event: ScheduledEvent, occurrence?: string) {
- console.log('▶️ NostrFeed: Starting task:', event.title)
- try {
- await startTask(event, '', occurrence)
- } catch (error) {
- console.error('❌ Failed to start task:', error)
- }
-}
-
-async function onCompleteTask(event: ScheduledEvent, occurrence?: string) {
- console.log('✅ NostrFeed: Completing task:', event.title)
- try {
- await completeEvent(event, occurrence, '')
- } catch (error) {
- console.error('❌ Failed to complete task:', error)
- }
-}
-
-async function onUnclaimTask(event: ScheduledEvent, occurrence?: string) {
- console.log('🔙 NostrFeed: Unclaiming task:', event.title)
- try {
- await unclaimTask(event, occurrence)
- } catch (error) {
- console.error('❌ Failed to unclaim task:', error)
- }
-}
-
-async function onDeleteTask(event: ScheduledEvent) {
- console.log('🗑️ NostrFeed: Deleting task:', event.title)
- try {
- await deleteTask(event)
- } catch (error) {
- console.error('❌ Failed to delete task:', error)
+ console.error('❌ NostrFeed: Failed to toggle event completion:', error)
}
}
@@ -511,7 +466,7 @@ function cancelDelete() {
-
+
@@ -551,7 +506,7 @@ function cancelDelete() {
-
+
diff --git a/src/modules/nostr-feed/components/ScheduledEventCard.vue b/src/modules/nostr-feed/components/ScheduledEventCard.vue
index 46c188e..dfc48af 100644
--- a/src/modules/nostr-feed/components/ScheduledEventCard.vue
+++ b/src/modules/nostr-feed/components/ScheduledEventCard.vue
@@ -2,7 +2,6 @@
import { computed, ref } from 'vue'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
-import { Checkbox } from '@/components/ui/checkbox'
import {
Dialog,
DialogContent,
@@ -16,25 +15,18 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible'
-import { Calendar, MapPin, Clock, CheckCircle, PlayCircle, Hand, Trash2 } from 'lucide-vue-next'
-import type { ScheduledEvent, EventCompletion, TaskStatus } from '../services/ScheduledEventService'
-import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { AuthService } from '@/modules/base/auth/auth-service'
+import { Calendar, MapPin, Clock, CheckCircle } from 'lucide-vue-next'
+import type { ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
interface Props {
event: ScheduledEvent
getDisplayName: (pubkey: string) => string
getCompletion: (eventAddress: string, occurrence?: string) => EventCompletion | undefined
- getTaskStatus: (eventAddress: string, occurrence?: string) => TaskStatus | null
adminPubkeys?: string[]
}
interface Emits {
- (e: 'claim-task', event: ScheduledEvent, occurrence?: string): void
- (e: 'start-task', event: ScheduledEvent, occurrence?: string): void
- (e: 'complete-task', event: ScheduledEvent, occurrence?: string): void
- (e: 'unclaim-task', event: ScheduledEvent, occurrence?: string): void
- (e: 'delete-task', event: ScheduledEvent): void
+ (e: 'toggle-complete', event: ScheduledEvent, occurrence?: string): void
}
const props = withDefaults(defineProps
(), {
@@ -43,12 +35,8 @@ const props = withDefaults(defineProps(), {
const emit = defineEmits()
-// Get auth service to check current user
-const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE)
-
// Confirmation dialog state
const showConfirmDialog = ref(false)
-const hasConfirmedCommunication = ref(false)
// Event address for tracking completion
const eventAddress = computed(() => `31922:${props.event.pubkey}:${props.event.dTag}`)
@@ -65,46 +53,12 @@ const occurrence = computed(() => {
// Check if this is an admin event
const isAdminEvent = computed(() => props.adminPubkeys.includes(props.event.pubkey))
-// Get current task status
-const taskStatus = computed(() => props.getTaskStatus(eventAddress.value, occurrence.value))
+// Check if event is completed - call function with occurrence for recurring events
+const isCompleted = computed(() => props.getCompletion(eventAddress.value, occurrence.value)?.completed || false)
// Check if event is completable (task type)
const isCompletable = computed(() => props.event.eventType === 'task')
-// Get completion data
-const completion = computed(() => props.getCompletion(eventAddress.value, occurrence.value))
-
-// Get current user's pubkey
-const currentUserPubkey = computed(() => authService?.user.value?.pubkey)
-
-// Check if current user can unclaim
-// Only show unclaim for "claimed" state, and only if current user is the one who claimed it
-const canUnclaim = computed(() => {
- if (!completion.value || !currentUserPubkey.value) return false
- if (taskStatus.value !== 'claimed') return false
- return completion.value.pubkey === currentUserPubkey.value
-})
-
-// Check if current user is the author of the task
-const isAuthor = computed(() => {
- if (!currentUserPubkey.value) return false
- return props.event.pubkey === currentUserPubkey.value
-})
-
-// Status badges configuration
-const statusConfig = computed(() => {
- switch (taskStatus.value) {
- case 'claimed':
- return { label: 'Claimed', variant: 'secondary' as const, icon: Hand, color: 'text-blue-600' }
- case 'in-progress':
- return { label: 'In Progress', variant: 'default' as const, icon: PlayCircle, color: 'text-orange-600' }
- case 'completed':
- return { label: 'Completed', variant: 'secondary' as const, icon: CheckCircle, color: 'text-green-600' }
- default:
- return null
- }
-})
-
// Format the date/time
const formattedDate = computed(() => {
try {
@@ -156,124 +110,28 @@ const formattedTimeRange = computed(() => {
}
})
-// Action type for confirmation dialog
-const pendingAction = ref<'claim' | 'start' | 'complete' | 'unclaim' | 'delete' | null>(null)
-
-// Handle claim task
-function handleClaimTask() {
- pendingAction.value = 'claim'
+// Handle mark complete button click - show confirmation dialog
+function handleMarkComplete() {
+ console.log('🔘 Mark Complete button clicked for event:', props.event.title)
showConfirmDialog.value = true
}
-// Handle start task
-function handleStartTask() {
- pendingAction.value = 'start'
- showConfirmDialog.value = true
-}
-
-// Handle complete task
-function handleCompleteTask() {
- pendingAction.value = 'complete'
- showConfirmDialog.value = true
-}
-
-// Handle unclaim task
-function handleUnclaimTask() {
- pendingAction.value = 'unclaim'
- showConfirmDialog.value = true
-}
-
-// Handle delete task
-function handleDeleteTask() {
- pendingAction.value = 'delete'
- showConfirmDialog.value = true
-}
-
-// Confirm action
-function confirmAction() {
- if (!pendingAction.value) return
-
- // For unclaim action, require checkbox confirmation
- if (pendingAction.value === 'unclaim' && !hasConfirmedCommunication.value) {
- return
- }
-
- switch (pendingAction.value) {
- case 'claim':
- emit('claim-task', props.event, occurrence.value)
- break
- case 'start':
- emit('start-task', props.event, occurrence.value)
- break
- case 'complete':
- emit('complete-task', props.event, occurrence.value)
- break
- case 'unclaim':
- emit('unclaim-task', props.event, occurrence.value)
- break
- case 'delete':
- emit('delete-task', props.event)
- break
- }
-
+// Confirm and execute mark complete
+function confirmMarkComplete() {
+ console.log('✅ Confirmed mark complete for event:', props.event.title, 'occurrence:', occurrence.value)
+ emit('toggle-complete', props.event, occurrence.value)
showConfirmDialog.value = false
- pendingAction.value = null
- hasConfirmedCommunication.value = false
}
-// Cancel action
-function cancelAction() {
+// Cancel mark complete
+function cancelMarkComplete() {
showConfirmDialog.value = false
- pendingAction.value = null
- hasConfirmedCommunication.value = false
}
-
-// Get dialog content based on pending action
-const dialogContent = computed(() => {
- switch (pendingAction.value) {
- case 'claim':
- return {
- title: 'Claim Task?',
- description: `This will mark "${props.event.title}" as claimed by you. You can start working on it later.`,
- confirmText: 'Claim Task'
- }
- case 'start':
- return {
- title: 'Start Task?',
- description: `This will mark "${props.event.title}" as in-progress. Others will see you're actively working on it.`,
- confirmText: 'Start Task'
- }
- case 'complete':
- return {
- title: 'Complete Task?',
- description: `This will mark "${props.event.title}" as completed by you. Other users will be able to see that you completed this task.`,
- confirmText: 'Mark Complete'
- }
- case 'unclaim':
- return {
- title: 'Unclaim Task?',
- description: `This will remove your claim on "${props.event.title}" and make it available for others.\n\nHave you communicated to others that you are unclaiming this task?`,
- confirmText: 'Unclaim Task'
- }
- case 'delete':
- return {
- title: 'Delete Task?',
- description: `This will permanently delete "${props.event.title}". This action cannot be undone.`,
- confirmText: 'Delete Task'
- }
- default:
- return {
- title: '',
- description: '',
- confirmText: ''
- }
- }
-})
+ :class="{ 'opacity-60': isCompletable && isCompleted }">
@@ -285,50 +143,26 @@ const dialogContent = computed(() => {
+ :class="{ 'line-through': isCompletable && isCompleted }">
{{ event.title }}
-
+
-
-
-
-
-
-
-
- {{ getDisplayName(completion.pubkey) }}
+
+
+ ✓ {{ getDisplayName(getCompletion(eventAddress, occurrence)!.pubkey) }}
@@ -366,20 +200,10 @@ const dialogContent = computed(() => {
{{ event.description || event.content }}
-
-
-
- ✓ Completed by {{ getDisplayName(completion.pubkey) }}
- - {{ completion.notes }}
-
-
- 🔄 In Progress by {{ getDisplayName(completion.pubkey) }}
- - {{ completion.notes }}
-
-
- 👋 Claimed by {{ getDisplayName(completion.pubkey) }}
- - {{ completion.notes }}
-
+
+
+ ✓ Completed by {{ getDisplayName(getCompletion(eventAddress, occurrence)!.pubkey) }}
+ - {{ getCompletion(eventAddress, occurrence)!.notes }}
@@ -387,113 +211,16 @@ const dialogContent = computed(() => {
Posted by {{ getDisplayName(event.pubkey) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -505,35 +232,14 @@ const dialogContent = computed(() => {
diff --git a/src/modules/nostr-feed/composables/useScheduledEvents.ts b/src/modules/nostr-feed/composables/useScheduledEvents.ts
index 580a26b..e56c002 100644
--- a/src/modules/nostr-feed/composables/useScheduledEvents.ts
+++ b/src/modules/nostr-feed/composables/useScheduledEvents.ts
@@ -1,6 +1,6 @@
import { computed } from 'vue'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ScheduledEventService, ScheduledEvent, EventCompletion, TaskStatus } from '../services/ScheduledEventService'
+import type { ScheduledEventService, ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
import type { AuthService } from '@/modules/base/auth/auth-service'
import { useToast } from '@/core/composables/useToast'
@@ -64,78 +64,8 @@ export function useScheduledEvents() {
return scheduledEventService.isCompleted(eventAddress)
}
- /**
- * Get task status for an event
- */
- const getTaskStatus = (eventAddress: string, occurrence?: string): TaskStatus | null => {
- if (!scheduledEventService) return null
- return scheduledEventService.getTaskStatus(eventAddress, occurrence)
- }
-
- /**
- * Claim a task
- */
- const claimTask = async (event: ScheduledEvent, notes: string = '', occurrence?: string): Promise
=> {
- if (!scheduledEventService) {
- toast.error('Scheduled event service not available')
- return
- }
-
- try {
- await scheduledEventService.claimTask(event, notes, occurrence)
- toast.success('Task claimed!')
- } catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to claim task'
- if (message.includes('authenticated')) {
- toast.error('Please sign in to claim tasks')
- } else {
- toast.error(message)
- }
- console.error('Failed to claim task:', error)
- }
- }
-
- /**
- * Start a task (mark as in-progress)
- */
- const startTask = async (event: ScheduledEvent, notes: string = '', occurrence?: string): Promise => {
- if (!scheduledEventService) {
- toast.error('Scheduled event service not available')
- return
- }
-
- try {
- await scheduledEventService.startTask(event, notes, occurrence)
- toast.success('Task started!')
- } catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to start task'
- toast.error(message)
- console.error('Failed to start task:', error)
- }
- }
-
- /**
- * Unclaim a task (remove task status)
- */
- const unclaimTask = async (event: ScheduledEvent, occurrence?: string): Promise => {
- if (!scheduledEventService) {
- toast.error('Scheduled event service not available')
- return
- }
-
- try {
- await scheduledEventService.unclaimTask(event, occurrence)
- toast.success('Task unclaimed')
- } catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to unclaim task'
- toast.error(message)
- console.error('Failed to unclaim task:', error)
- }
- }
-
/**
* Toggle completion status of an event (optionally for a specific occurrence)
- * DEPRECATED: Use claimTask, startTask, completeEvent, or unclaimTask instead for more granular control
*/
const toggleComplete = async (event: ScheduledEvent, occurrence?: string, notes: string = ''): Promise => {
console.log('🔧 useScheduledEvents: toggleComplete called for event:', event.title, 'occurrence:', occurrence)
@@ -152,19 +82,19 @@ export function useScheduledEvents() {
console.log('📊 useScheduledEvents: Current completion status:', currentlyCompleted)
if (currentlyCompleted) {
- console.log('⬇️ useScheduledEvents: Unclaiming task...')
- await scheduledEventService.unclaimTask(event, occurrence)
- toast.success('Task unclaimed')
+ console.log('⬇️ useScheduledEvents: Marking as incomplete...')
+ await scheduledEventService.uncompleteEvent(event, occurrence)
+ toast.success('Event marked as incomplete')
} else {
console.log('⬆️ useScheduledEvents: Marking as complete...')
await scheduledEventService.completeEvent(event, notes, occurrence)
- toast.success('Task completed!')
+ toast.success('Event completed!')
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to toggle completion'
if (message.includes('authenticated')) {
- toast.error('Please sign in to complete tasks')
+ toast.error('Please sign in to complete events')
} else if (message.includes('Not connected')) {
toast.error('Not connected to relays')
} else {
@@ -178,19 +108,19 @@ export function useScheduledEvents() {
/**
* Complete an event with optional notes
*/
- const completeEvent = async (event: ScheduledEvent, occurrence?: string, notes: string = ''): Promise => {
+ const completeEvent = async (event: ScheduledEvent, notes: string = ''): Promise => {
if (!scheduledEventService) {
toast.error('Scheduled event service not available')
return
}
try {
- await scheduledEventService.completeEvent(event, notes, occurrence)
- toast.success('Task completed!')
+ await scheduledEventService.completeEvent(event, notes)
+ toast.success('Event completed!')
} catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to complete task'
+ const message = error instanceof Error ? error.message : 'Failed to complete event'
toast.error(message)
- console.error('Failed to complete task:', error)
+ console.error('Failed to complete event:', error)
}
}
@@ -208,25 +138,6 @@ export function useScheduledEvents() {
return scheduledEventService?.scheduledEvents ?? new Map()
})
- /**
- * Delete a task (only author can delete)
- */
- const deleteTask = async (event: ScheduledEvent): Promise => {
- if (!scheduledEventService) {
- toast.error('Scheduled event service not available')
- return
- }
-
- try {
- await scheduledEventService.deleteTask(event)
- toast.success('Task deleted!')
- } catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to delete task'
- toast.error(message)
- console.error('Failed to delete task:', error)
- }
- }
-
/**
* Get all completions (reactive) - returns array for better reactivity
*/
@@ -236,22 +147,15 @@ export function useScheduledEvents() {
})
return {
- // Methods - Getters
+ // Methods
getScheduledEvents,
getEventsForDate,
getEventsForSpecificDate,
getTodaysEvents,
getCompletion,
isCompleted,
- getTaskStatus,
-
- // Methods - Actions
- claimTask,
- startTask,
+ toggleComplete,
completeEvent,
- unclaimTask,
- deleteTask,
- toggleComplete, // DEPRECATED: Use specific actions instead
// State
isLoading,
diff --git a/src/modules/nostr-feed/services/FeedService.ts b/src/modules/nostr-feed/services/FeedService.ts
index 2141ed7..500f963 100644
--- a/src/modules/nostr-feed/services/FeedService.ts
+++ b/src/modules/nostr-feed/services/FeedService.ts
@@ -383,28 +383,6 @@ export class FeedService extends BaseService {
return
}
- // Route to ScheduledEventService for completion/RSVP deletions (kind 31925)
- if (deletedKind === '31925') {
- console.log('🔀 FeedService: Routing kind 5 (deletion of kind 31925) to ScheduledEventService')
- if (this.scheduledEventService) {
- this.scheduledEventService.handleDeletionEvent(event)
- } else {
- console.warn('⚠️ FeedService: ScheduledEventService not available')
- }
- return
- }
-
- // Route to ScheduledEventService for scheduled event deletions (kind 31922)
- if (deletedKind === '31922') {
- console.log('🔀 FeedService: Routing kind 5 (deletion of kind 31922) to ScheduledEventService')
- if (this.scheduledEventService) {
- this.scheduledEventService.handleTaskDeletion(event)
- } else {
- console.warn('⚠️ FeedService: ScheduledEventService not available')
- }
- return
- }
-
// Handle post deletions (kind 1) in FeedService
if (deletedKind === '1' || !deletedKind) {
// Extract event IDs to delete from 'e' tags
diff --git a/src/modules/nostr-feed/services/ScheduledEventService.ts b/src/modules/nostr-feed/services/ScheduledEventService.ts
index d2ef6b4..21af01f 100644
--- a/src/modules/nostr-feed/services/ScheduledEventService.ts
+++ b/src/modules/nostr-feed/services/ScheduledEventService.ts
@@ -28,16 +28,14 @@ export interface ScheduledEvent {
recurrence?: RecurrencePattern // Optional: for recurring events
}
-export type TaskStatus = 'claimed' | 'in-progress' | 'completed' | 'blocked' | 'cancelled'
-
export interface EventCompletion {
id: string
eventAddress: string // "31922:pubkey:d-tag"
occurrence?: string // ISO date string for the specific occurrence (YYYY-MM-DD)
- pubkey: string // Who claimed/completed it
+ pubkey: string // Who completed it
created_at: number
- taskStatus: TaskStatus
- completedAt?: number // Unix timestamp when completed
+ completed: boolean
+ completedAt?: number
notes: string
}
@@ -160,19 +158,7 @@ export class ScheduledEventService extends BaseService {
return
}
- // Parse task status (new approach)
- const taskStatusTag = event.tags.find(tag => tag[0] === 'task-status')?.[1] as TaskStatus | undefined
-
- // Backward compatibility: check old 'completed' tag if task-status not present
- let taskStatus: TaskStatus
- if (taskStatusTag) {
- taskStatus = taskStatusTag
- } else {
- // Legacy support: convert old 'completed' tag to new taskStatus
- const completed = event.tags.find(tag => tag[0] === 'completed')?.[1] === 'true'
- taskStatus = completed ? 'completed' : 'claimed'
- }
-
+ const completed = event.tags.find(tag => tag[0] === 'completed')?.[1] === 'true'
const completedAtTag = event.tags.find(tag => tag[0] === 'completed_at')?.[1]
const completedAt = completedAtTag ? parseInt(completedAtTag) : undefined
const occurrence = event.tags.find(tag => tag[0] === 'occurrence')?.[1] // ISO date string
@@ -180,7 +166,7 @@ export class ScheduledEventService extends BaseService {
console.log('📋 Completion details:', {
aTag,
occurrence,
- taskStatus,
+ completed,
pubkey: event.pubkey,
eventId: event.id
})
@@ -191,7 +177,7 @@ export class ScheduledEventService extends BaseService {
occurrence,
pubkey: event.pubkey,
created_at: event.created_at,
- taskStatus,
+ completed,
completedAt,
notes: event.content
}
@@ -203,7 +189,7 @@ export class ScheduledEventService extends BaseService {
const existing = this._completions.get(completionKey)
if (!existing || event.created_at > existing.created_at) {
this._completions.set(completionKey, completion)
- console.log('✅ Stored completion for:', completionKey, '- status:', taskStatus)
+ console.log('✅ Stored completion for:', completionKey, '- completed:', completed)
} else {
console.log('⏭️ Skipped older completion for:', completionKey)
}
@@ -213,90 +199,6 @@ export class ScheduledEventService extends BaseService {
}
}
- /**
- * Handle deletion event (kind 5) for completion events
- * Made public so FeedService can route deletion events to this service
- */
- public handleDeletionEvent(event: NostrEvent): void {
- console.log('🗑️ ScheduledEventService: Received deletion event (kind 5)', event.id)
-
- try {
- // Extract event IDs to delete from 'e' tags
- const eventIdsToDelete = event.tags
- ?.filter((tag: string[]) => tag[0] === 'e')
- .map((tag: string[]) => tag[1]) || []
-
- if (eventIdsToDelete.length === 0) {
- console.warn('Deletion event missing e tags:', event.id)
- return
- }
-
- console.log('🔍 Looking for completions to delete:', eventIdsToDelete)
-
- // Find and remove completions that match the deleted event IDs
- let deletedCount = 0
- for (const [completionKey, completion] of this._completions.entries()) {
- // Only delete if:
- // 1. The completion event ID matches one being deleted
- // 2. The deletion request comes from the same author (NIP-09 validation)
- if (eventIdsToDelete.includes(completion.id) && completion.pubkey === event.pubkey) {
- this._completions.delete(completionKey)
- console.log('✅ Deleted completion:', completionKey, 'event ID:', completion.id)
- deletedCount++
- }
- }
-
- console.log(`🗑️ Deleted ${deletedCount} completion(s) from deletion event`)
-
- } catch (error) {
- console.error('Failed to handle deletion event:', error)
- }
- }
-
- /**
- * Handle deletion event (kind 5) for scheduled events (kind 31922)
- * Made public so FeedService can route deletion events to this service
- */
- public handleTaskDeletion(event: NostrEvent): void {
- console.log('🗑️ ScheduledEventService: Received task deletion event (kind 5)', event.id)
-
- try {
- // Extract event addresses to delete from 'a' tags
- const eventAddressesToDelete = event.tags
- ?.filter((tag: string[]) => tag[0] === 'a')
- .map((tag: string[]) => tag[1]) || []
-
- if (eventAddressesToDelete.length === 0) {
- console.warn('Task deletion event missing a tags:', event.id)
- return
- }
-
- console.log('🔍 Looking for tasks to delete:', eventAddressesToDelete)
-
- // Find and remove tasks that match the deleted event addresses
- let deletedCount = 0
- for (const eventAddress of eventAddressesToDelete) {
- const task = this._scheduledEvents.get(eventAddress)
-
- // Only delete if:
- // 1. The task exists
- // 2. The deletion request comes from the task author (NIP-09 validation)
- if (task && task.pubkey === event.pubkey) {
- this._scheduledEvents.delete(eventAddress)
- console.log('✅ Deleted task:', eventAddress)
- deletedCount++
- } else if (task) {
- console.warn('⚠️ Deletion request not from task author:', eventAddress)
- }
- }
-
- console.log(`🗑️ Deleted ${deletedCount} task(s) from deletion event`)
-
- } catch (error) {
- console.error('Failed to handle task deletion event:', error)
- }
- }
-
/**
* Get all scheduled events
*/
@@ -408,49 +310,15 @@ export class ScheduledEventService extends BaseService {
*/
isCompleted(eventAddress: string, occurrence?: string): boolean {
const completion = this.getCompletion(eventAddress, occurrence)
- return completion?.taskStatus === 'completed'
- }
-
- /**
- * Get task status for an event
- */
- getTaskStatus(eventAddress: string, occurrence?: string): TaskStatus | null {
- const completion = this.getCompletion(eventAddress, occurrence)
- return completion?.taskStatus || null
- }
-
- /**
- * Claim a task (mark as claimed)
- */
- async claimTask(event: ScheduledEvent, notes: string = '', occurrence?: string): Promise {
- await this.updateTaskStatus(event, 'claimed', notes, occurrence)
- }
-
- /**
- * Start a task (mark as in-progress)
- */
- async startTask(event: ScheduledEvent, notes: string = '', occurrence?: string): Promise {
- await this.updateTaskStatus(event, 'in-progress', notes, occurrence)
+ return completion?.completed || false
}
/**
* Mark an event as complete (optionally for a specific occurrence)
*/
async completeEvent(event: ScheduledEvent, notes: string = '', occurrence?: string): Promise {
- await this.updateTaskStatus(event, 'completed', notes, occurrence)
- }
-
- /**
- * Internal method to update task status
- */
- private async updateTaskStatus(
- event: ScheduledEvent,
- taskStatus: TaskStatus,
- notes: string = '',
- occurrence?: string
- ): Promise {
if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to update task status')
+ throw new Error('Must be authenticated to complete events')
}
if (!this.relayHub?.isConnected) {
@@ -467,17 +335,14 @@ export class ScheduledEventService extends BaseService {
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
- // Create RSVP event with task-status tag
+ // Create RSVP/completion event (NIP-52)
const tags: string[][] = [
['a', eventAddress],
- ['task-status', taskStatus]
+ ['status', 'accepted'],
+ ['completed', 'true'],
+ ['completed_at', Math.floor(Date.now() / 1000).toString()]
]
- // Add completed_at timestamp if task is completed
- if (taskStatus === 'completed') {
- tags.push(['completed_at', Math.floor(Date.now() / 1000).toString()])
- }
-
// Add occurrence tag if provided (for recurring events)
if (occurrence) {
tags.push(['occurrence', occurrence])
@@ -494,17 +359,17 @@ export class ScheduledEventService extends BaseService {
const privkeyBytes = this.hexToUint8Array(userPrivkey)
const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
- // Publish the status update
- console.log(`📤 Publishing task status update (${taskStatus}) for:`, eventAddress)
+ // Publish the completion
+ console.log('📤 Publishing completion event (kind 31925) for:', eventAddress)
const result = await this.relayHub.publishEvent(signedEvent)
- console.log('✅ Task status published to', result.success, '/', result.total, 'relays')
+ console.log('✅ Completion event published to', result.success, '/', result.total, 'relays')
- // Update local state (publishEvent throws if no relays accepted)
- console.log('🔄 Updating local state (event published successfully)')
+ // Optimistically update local state
+ console.log('🔄 Optimistically updating local state')
this.handleCompletionEvent(signedEvent)
} catch (error) {
- console.error('Failed to update task status:', error)
+ console.error('Failed to complete event:', error)
throw error
} finally {
this._isLoading.value = false
@@ -512,13 +377,11 @@ export class ScheduledEventService extends BaseService {
}
/**
- * Unclaim/reset a task (removes task status - makes it unclaimed)
- * Note: In Nostr, we can't truly "delete" an event, but we can publish
- * a deletion request (kind 5) to ask relays to remove our RSVP
+ * Uncomplete an event (publish new RSVP with completed=false)
*/
- async unclaimTask(event: ScheduledEvent, occurrence?: string): Promise {
+ async uncompleteEvent(event: ScheduledEvent, occurrence?: string): Promise {
if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to unclaim tasks')
+ throw new Error('Must be authenticated to uncomplete events')
}
if (!this.relayHub?.isConnected) {
@@ -534,102 +397,38 @@ export class ScheduledEventService extends BaseService {
this._isLoading.value = true
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
- const completionKey = occurrence ? `${eventAddress}:${occurrence}` : eventAddress
- const completion = this._completions.get(completionKey)
- if (!completion) {
- console.log('No completion to unclaim')
- return
+ // Create RSVP event with completed=false
+ const tags: string[][] = [
+ ['a', eventAddress],
+ ['status', 'tentative'],
+ ['completed', 'false']
+ ]
+
+ // Add occurrence tag if provided (for recurring events)
+ if (occurrence) {
+ tags.push(['occurrence', occurrence])
}
- // Create deletion event (kind 5) for the RSVP
- const deletionEvent: EventTemplate = {
- kind: 5,
- content: 'Task unclaimed',
- tags: [
- ['e', completion.id], // Reference to the RSVP event being deleted
- ['k', '31925'] // Kind of event being deleted
- ],
+ const eventTemplate: EventTemplate = {
+ kind: 31925,
+ content: '',
+ tags,
created_at: Math.floor(Date.now() / 1000)
}
// Sign the event
const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(deletionEvent, privkeyBytes)
+ const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
- // Publish the deletion request
- console.log('📤 Publishing deletion request for task RSVP:', completion.id)
- const result = await this.relayHub.publishEvent(signedEvent)
- console.log('✅ Deletion request published to', result.success, '/', result.total, 'relays')
+ // Publish the uncomplete
+ await this.relayHub.publishEvent(signedEvent)
- // Remove from local state (publishEvent throws if no relays accepted)
- this._completions.delete(completionKey)
- console.log('🗑️ Removed completion from local state:', completionKey)
+ // Optimistically update local state
+ this.handleCompletionEvent(signedEvent)
} catch (error) {
- console.error('Failed to unclaim task:', error)
- throw error
- } finally {
- this._isLoading.value = false
- }
- }
-
- /**
- * Delete a scheduled event (kind 31922)
- * Only the author can delete their own event
- */
- async deleteTask(event: ScheduledEvent): Promise {
- if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to delete tasks')
- }
-
- if (!this.relayHub?.isConnected) {
- throw new Error('Not connected to relays')
- }
-
- const userPrivkey = this.authService.user.value?.prvkey
- const userPubkey = this.authService.user.value?.pubkey
-
- if (!userPrivkey || !userPubkey) {
- throw new Error('User credentials not available')
- }
-
- // Only author can delete
- if (userPubkey !== event.pubkey) {
- throw new Error('Only the task author can delete this task')
- }
-
- try {
- this._isLoading.value = true
-
- const eventAddress = `31922:${event.pubkey}:${event.dTag}`
-
- // Create deletion event (kind 5) for the scheduled event
- const deletionEvent: EventTemplate = {
- kind: 5,
- content: 'Task deleted',
- tags: [
- ['a', eventAddress], // Reference to the parameterized replaceable event being deleted
- ['k', '31922'] // Kind of event being deleted
- ],
- created_at: Math.floor(Date.now() / 1000)
- }
-
- // Sign the event
- const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(deletionEvent, privkeyBytes)
-
- // Publish the deletion request
- console.log('📤 Publishing deletion request for task:', eventAddress)
- const result = await this.relayHub.publishEvent(signedEvent)
- console.log('✅ Task deletion request published to', result.success, '/', result.total, 'relays')
-
- // Remove from local state (publishEvent throws if no relays accepted)
- this._scheduledEvents.delete(eventAddress)
- console.log('🗑️ Removed task from local state:', eventAddress)
-
- } catch (error) {
- console.error('Failed to delete task:', error)
+ console.error('Failed to uncomplete event:', error)
throw error
} finally {
this._isLoading.value = false