'use client' import * as actions from './actions' /** * Offline-aware wrapper for server actions * Queues mutations when offline and executes them when online * Validates: Requirements 4.5 */ /** * Get the mutation queue manager * This is a helper to access the queue without circular dependencies */ function getMutationQueue() { if (typeof window === 'undefined') return null try { const queue = localStorage.getItem('mutation-queue') return queue ? JSON.parse(queue) : [] } catch (error) { console.error('Failed to get mutation queue:', error) return [] } } /** * Add a mutation to the queue */ function queueMutation(mutation: { type: 'create' | 'update' | 'delete' entity: 'list' | 'item' data: any }) { if (typeof window === 'undefined') return try { const queue = getMutationQueue() const newMutation = { ...mutation, id: `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, timestamp: Date.now(), retries: 0, } queue.push(newMutation) localStorage.setItem('mutation-queue', JSON.stringify(queue)) // Dispatch custom event to notify useOfflineSync hook window.dispatchEvent(new CustomEvent('mutation-queued')) } catch (error) { console.error('Failed to queue mutation:', error) } } /** * Check if we're online */ function isOnline(): boolean { return typeof window !== 'undefined' && navigator.onLine } /** * Offline-aware createList */ export async function createListOffline(title: string, color: string = '#3B82F6') { if (!isOnline()) { queueMutation({ type: 'create', entity: 'list', data: { title, color }, }) // Return a temporary list object for optimistic UI return { id: `temp-${Date.now()}`, title, color, createdAt: new Date(), updatedAt: new Date(), items: [], } } return await actions.createList(title, color) } /** * Offline-aware updateList */ export async function updateListOffline( listId: string, data: { title?: string; color?: string } ) { if (!isOnline()) { queueMutation({ type: 'update', entity: 'list', data: { id: listId, ...data }, }) return { id: listId, ...data } } return await actions.updateList(listId, data) } /** * Offline-aware deleteList */ export async function deleteListOffline(listId: string) { if (!isOnline()) { queueMutation({ type: 'delete', entity: 'list', data: { id: listId }, }) return { success: true } } return await actions.deleteList(listId) } /** * Offline-aware createItem */ export async function createItemOffline( listId: string, title: string, description?: string, metadata?: { priority?: 'low' | 'medium' | 'high' dueDate?: Date | null tags?: string[] notes?: string | null } ) { if (!isOnline()) { queueMutation({ type: 'create', entity: 'item', data: { listId, title, description, metadata }, }) // Return a temporary item object for optimistic UI return { id: `temp-${Date.now()}`, title, description: description || null, completed: false, priority: metadata?.priority || 'medium', dueDate: metadata?.dueDate || null, tags: metadata?.tags || [], notes: metadata?.notes || null, createdAt: new Date(), updatedAt: new Date(), listId, } } return await actions.createItem(listId, title, description, metadata) } /** * Offline-aware updateItem */ export async function updateItemOffline( itemId: string, data: { title?: string description?: string | null completed?: boolean priority?: 'low' | 'medium' | 'high' dueDate?: Date | null tags?: string[] notes?: string | null } ) { if (!isOnline()) { queueMutation({ type: 'update', entity: 'item', data: { id: itemId, ...data }, }) return { id: itemId, ...data } } return await actions.updateItem(itemId, data) } /** * Offline-aware deleteItem */ export async function deleteItemOffline(itemId: string) { if (!isOnline()) { queueMutation({ type: 'delete', entity: 'item', data: { id: itemId }, }) return { success: true } } return await actions.deleteItem(itemId) } /** * Offline-aware toggleItem */ export async function toggleItemOffline(itemId: string) { if (!isOnline()) { // For toggle, we need to get the current state first // This is a limitation - we'll queue it as an update queueMutation({ type: 'update', entity: 'item', data: { id: itemId, completed: true }, // We don't know the actual state }) return null } return await actions.toggleItem(itemId) }