jedmund-svelte/src/lib/admin/useDraftRecovery.svelte.ts
Justin Edmund 34a3e370ec refactor(admin): modularize ProjectForm with composable stores
Extract reusable form patterns following Svelte 5 best practices:

**New Store Factory** (`project-form.svelte.ts`)
- Centralizes form state management with `$state` and `$derived` runes
- Provides validation, payload building, and field mutation methods
- Type-safe with ProjectFormData interface
- Reusable across different contexts

**New Helpers**
- `useDraftRecovery.svelte.ts`: Generic draft restoration with auto-detection
- `useFormGuards.svelte.ts`: Navigation guards, beforeunload warning, Cmd+S shortcut
- `DraftPrompt.svelte`: Extracted UI component for draft recovery prompts

**Refactored ProjectForm.svelte**
- Reduced from 720 lines to 417 lines (42% reduction)
- Uses new composable helpers instead of inline logic
- Cleaner separation between UI orchestration and business logic
- All form state now managed through formStore
- Draft recovery, navigation guards fully extracted

**Benefits**
- Reusable patterns for PostForm, EssayForm, etc.
- Easier to test helpers in isolation
- Consistent UX across all admin forms
- Better maintainability and code organization

Closes Task 3 of admin modernization plan (Phase 2)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-07 23:24:50 -07:00

61 lines
1.4 KiB
TypeScript

import { loadDraft, clearDraft, timeAgo } from '$lib/admin/draftStore'
export function useDraftRecovery<TPayload>(options: {
draftKey: string | null
onRestore: (payload: TPayload) => void
enabled?: boolean
}) {
// Reactive state using $state rune
let showPrompt = $state(false)
let draftTimestamp = $state<number | null>(null)
let timeTicker = $state(0)
// Derived state for time display
const draftTimeText = $derived.by(() =>
draftTimestamp ? (timeTicker, timeAgo(draftTimestamp)) : null
)
// Auto-detect draft on mount using $effect
$effect(() => {
if (!options.draftKey || options.enabled === false) return
const draft = loadDraft<TPayload>(options.draftKey)
if (draft) {
showPrompt = true
draftTimestamp = draft.ts
}
})
// Update time display every minute using $effect
$effect(() => {
if (!showPrompt) return
const interval = setInterval(() => {
timeTicker = timeTicker + 1
}, 60000)
return () => clearInterval(interval)
})
return {
// State returned directly - reactive in Svelte 5
showPrompt,
draftTimeText,
restore() {
if (!options.draftKey) return
const draft = loadDraft<TPayload>(options.draftKey)
if (!draft) return
options.onRestore(draft.payload)
showPrompt = false
clearDraft(options.draftKey)
},
dismiss() {
if (!options.draftKey) return
showPrompt = false
clearDraft(options.draftKey)
}
}
}