Added ability for task authors to delete their own tasks from the expanded view in the task feed. **Features:** - Delete button visible only to task author in expanded task view - Confirmation dialog with destructive styling - Publishes NIP-09 deletion event (kind 5) with 'a' tag referencing the task's event address (kind:pubkey:d-tag format) - Real-time deletion handling via FeedService routing - Optimistic local state update for immediate UI feedback **Implementation:** - Added deleteTask() method to ScheduledEventService - Added handleTaskDeletion() for processing incoming deletion events - Updated FeedService to route kind 31922 deletions to ScheduledEventService - Added delete button and dialog flow to ScheduledEventCard component - Integrated with existing confirmation dialog pattern **Permissions:** - Only task authors can delete tasks (enforced by isAuthor check) - NIP-09 validation: relays only accept deletion from event author - Pubkey verification in handleTaskDeletion() **Testing:** - Created tasks and verified delete button appears for author only - Confirmed deletion removes task from UI immediately - Verified deletion persists after refresh - Tested with multiple users - others cannot delete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
261 lines
8.1 KiB
TypeScript
261 lines
8.1 KiB
TypeScript
import { computed } from 'vue'
|
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
|
import type { ScheduledEventService, ScheduledEvent, EventCompletion, TaskStatus } from '../services/ScheduledEventService'
|
|
import type { AuthService } from '@/modules/base/auth/auth-service'
|
|
import { useToast } from '@/core/composables/useToast'
|
|
|
|
/**
|
|
* Composable for managing scheduled events in the feed
|
|
*/
|
|
export function useScheduledEvents() {
|
|
const scheduledEventService = injectService<ScheduledEventService>(SERVICE_TOKENS.SCHEDULED_EVENT_SERVICE)
|
|
const authService = injectService<AuthService>(SERVICE_TOKENS.AUTH_SERVICE)
|
|
const toast = useToast()
|
|
|
|
// Get current user's pubkey
|
|
const currentUserPubkey = computed(() => authService?.user.value?.pubkey)
|
|
|
|
/**
|
|
* Get all scheduled events
|
|
*/
|
|
const getScheduledEvents = (): ScheduledEvent[] => {
|
|
if (!scheduledEventService) return []
|
|
return scheduledEventService.getScheduledEvents()
|
|
}
|
|
|
|
/**
|
|
* Get events for a specific date (YYYY-MM-DD)
|
|
*/
|
|
const getEventsForDate = (date: string): ScheduledEvent[] => {
|
|
if (!scheduledEventService) return []
|
|
return scheduledEventService.getEventsForDate(date)
|
|
}
|
|
|
|
/**
|
|
* Get events for a specific date (filtered by current user participation)
|
|
* @param date - ISO date string (YYYY-MM-DD). Defaults to today.
|
|
*/
|
|
const getEventsForSpecificDate = (date?: string): ScheduledEvent[] => {
|
|
if (!scheduledEventService) return []
|
|
return scheduledEventService.getEventsForSpecificDate(date, currentUserPubkey.value)
|
|
}
|
|
|
|
/**
|
|
* Get today's scheduled events (filtered by current user participation)
|
|
*/
|
|
const getTodaysEvents = (): ScheduledEvent[] => {
|
|
if (!scheduledEventService) return []
|
|
return scheduledEventService.getTodaysEvents(currentUserPubkey.value)
|
|
}
|
|
|
|
/**
|
|
* Get completion status for an event
|
|
*/
|
|
const getCompletion = (eventAddress: string): EventCompletion | undefined => {
|
|
if (!scheduledEventService) return undefined
|
|
return scheduledEventService.getCompletion(eventAddress)
|
|
}
|
|
|
|
/**
|
|
* Check if an event is completed
|
|
*/
|
|
const isCompleted = (eventAddress: string): boolean => {
|
|
if (!scheduledEventService) return false
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
console.log('🔧 useScheduledEvents: toggleComplete called for event:', event.title, 'occurrence:', occurrence)
|
|
|
|
if (!scheduledEventService) {
|
|
console.error('❌ useScheduledEvents: Scheduled event service not available')
|
|
toast.error('Scheduled event service not available')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
|
|
const currentlyCompleted = scheduledEventService.isCompleted(eventAddress, occurrence)
|
|
console.log('📊 useScheduledEvents: Current completion status:', currentlyCompleted)
|
|
|
|
if (currentlyCompleted) {
|
|
console.log('⬇️ useScheduledEvents: Unclaiming task...')
|
|
await scheduledEventService.unclaimTask(event, occurrence)
|
|
toast.success('Task unclaimed')
|
|
} else {
|
|
console.log('⬆️ useScheduledEvents: Marking as complete...')
|
|
await scheduledEventService.completeEvent(event, notes, occurrence)
|
|
toast.success('Task 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')
|
|
} else if (message.includes('Not connected')) {
|
|
toast.error('Not connected to relays')
|
|
} else {
|
|
toast.error(message)
|
|
}
|
|
|
|
console.error('❌ useScheduledEvents: Failed to toggle completion:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete an event with optional notes
|
|
*/
|
|
const completeEvent = async (event: ScheduledEvent, occurrence?: string, notes: string = ''): Promise<void> => {
|
|
if (!scheduledEventService) {
|
|
toast.error('Scheduled event service not available')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await scheduledEventService.completeEvent(event, notes, occurrence)
|
|
toast.success('Task completed!')
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to complete task'
|
|
toast.error(message)
|
|
console.error('Failed to complete task:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get loading state
|
|
*/
|
|
const isLoading = computed(() => {
|
|
return scheduledEventService?.isLoading ?? false
|
|
})
|
|
|
|
/**
|
|
* Get all scheduled events (reactive)
|
|
*/
|
|
const allScheduledEvents = computed(() => {
|
|
return scheduledEventService?.scheduledEvents ?? new Map()
|
|
})
|
|
|
|
/**
|
|
* Delete a task (only author can delete)
|
|
*/
|
|
const deleteTask = async (event: ScheduledEvent): Promise<void> => {
|
|
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
|
|
*/
|
|
const allCompletions = computed(() => {
|
|
if (!scheduledEventService?.completions) return []
|
|
return Array.from(scheduledEventService.completions.values())
|
|
})
|
|
|
|
return {
|
|
// Methods - Getters
|
|
getScheduledEvents,
|
|
getEventsForDate,
|
|
getEventsForSpecificDate,
|
|
getTodaysEvents,
|
|
getCompletion,
|
|
isCompleted,
|
|
getTaskStatus,
|
|
|
|
// Methods - Actions
|
|
claimTask,
|
|
startTask,
|
|
completeEvent,
|
|
unclaimTask,
|
|
deleteTask,
|
|
toggleComplete, // DEPRECATED: Use specific actions instead
|
|
|
|
// State
|
|
isLoading,
|
|
allScheduledEvents,
|
|
allCompletions
|
|
}
|
|
}
|