feat: implement data migration for existing lists

- Created migration-actions.ts with server actions for claiming lists
- Added ClaimListsModal component for UI during registration
- Integrated claim modal into RegisterForm
- Created anonymous-lists.ts utilities for tracking lists in localStorage
- Added migrate-lists.ts script for deployment migration
- Created comprehensive MIGRATION_GUIDE.md documentation
- Added migration tests with 100% pass rate
- Updated package.json with db:migrate script
- Added tsx as dev dependency for running migration scripts

Requirements: 10.2, 10.3, 10.4, 10.5
- Users can claim existing lists on first registration
- All list data is preserved during migration
- All lists have an owner after migration
- Migrated lists appear in user's account
This commit is contained in:
2025-12-02 01:20:35 +00:00
parent 54612a4782
commit 2ce89e7c6c
9 changed files with 976 additions and 4 deletions

85
lib/anonymous-lists.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* Utilities for managing anonymous list IDs in localStorage
* This helps track lists created before user registration for migration
*
* Requirements: 10.2, 10.5 - Migration path for existing anonymous lists
*/
const STORAGE_KEY = 'anonymousListIds'
/**
* Add a list ID to the anonymous lists tracking
*/
export function trackAnonymousList(listId: string): void {
if (typeof window === 'undefined') return
try {
const stored = localStorage.getItem(STORAGE_KEY)
const listIds: string[] = stored ? JSON.parse(stored) : []
if (!listIds.includes(listId)) {
listIds.push(listId)
localStorage.setItem(STORAGE_KEY, JSON.stringify(listIds))
}
} catch (error) {
console.error('Error tracking anonymous list:', error)
}
}
/**
* Remove a list ID from anonymous lists tracking
*/
export function untrackAnonymousList(listId: string): void {
if (typeof window === 'undefined') return
try {
const stored = localStorage.getItem(STORAGE_KEY)
if (!stored) return
const listIds: string[] = JSON.parse(stored)
const filtered = listIds.filter(id => id !== listId)
if (filtered.length > 0) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered))
} else {
localStorage.removeItem(STORAGE_KEY)
}
} catch (error) {
console.error('Error untracking anonymous list:', error)
}
}
/**
* Get all tracked anonymous list IDs
*/
export function getAnonymousListIds(): string[] {
if (typeof window === 'undefined') return []
try {
const stored = localStorage.getItem(STORAGE_KEY)
return stored ? JSON.parse(stored) : []
} catch (error) {
console.error('Error getting anonymous list IDs:', error)
return []
}
}
/**
* Clear all tracked anonymous list IDs
*/
export function clearAnonymousLists(): void {
if (typeof window === 'undefined') return
try {
localStorage.removeItem(STORAGE_KEY)
} catch (error) {
console.error('Error clearing anonymous lists:', error)
}
}
/**
* Check if there are any tracked anonymous lists
*/
export function hasAnonymousLists(): boolean {
return getAnonymousListIds().length > 0
}