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>
61 lines
1.4 KiB
TypeScript
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)
|
|
}
|
|
}
|
|
}
|