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:
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'
|
||||
import { validateEmail, validatePassword, sanitizeString } from '@/lib/validation'
|
||||
import { registerUser } from '@/lib/auth-actions'
|
||||
import { useToast } from './ToastContainer'
|
||||
import ClaimListsModal from './ClaimListsModal'
|
||||
|
||||
interface RegisterFormProps {
|
||||
callbackUrl?: string
|
||||
@@ -18,6 +19,7 @@ export default function RegisterForm({ callbackUrl = '/' }: RegisterFormProps) {
|
||||
const [password, setPassword] = useState('')
|
||||
const [confirmPassword, setConfirmPassword] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [showClaimModal, setShowClaimModal] = useState(false)
|
||||
const [errors, setErrors] = useState<{
|
||||
name?: string
|
||||
email?: string
|
||||
@@ -154,8 +156,19 @@ export default function RegisterForm({ callbackUrl = '/' }: RegisterFormProps) {
|
||||
}
|
||||
} else {
|
||||
showToast('Registration successful! Logging you in...', 'success')
|
||||
router.push(callbackUrl)
|
||||
router.refresh()
|
||||
|
||||
// Check if there are lists to claim from localStorage
|
||||
// Requirement 10.2 - Add UI for users to claim existing lists on first registration
|
||||
const hasAnonymousLists = checkForAnonymousLists()
|
||||
|
||||
if (hasAnonymousLists) {
|
||||
// Show claim modal
|
||||
setShowClaimModal(true)
|
||||
} else {
|
||||
// No lists to claim, proceed to app
|
||||
router.push(callbackUrl)
|
||||
router.refresh()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Registration error:', err)
|
||||
@@ -165,8 +178,42 @@ export default function RegisterForm({ callbackUrl = '/' }: RegisterFormProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const checkForAnonymousLists = (): boolean => {
|
||||
try {
|
||||
const stored = localStorage.getItem('anonymousListIds')
|
||||
if (stored) {
|
||||
const listIds = JSON.parse(stored)
|
||||
return Array.isArray(listIds) && listIds.length > 0
|
||||
}
|
||||
return false
|
||||
} catch (err) {
|
||||
console.error('Error checking for anonymous lists:', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClaimComplete = () => {
|
||||
setShowClaimModal(false)
|
||||
showToast('Lists claimed successfully!', 'success')
|
||||
router.push(callbackUrl)
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
const handleClaimClose = () => {
|
||||
setShowClaimModal(false)
|
||||
router.push(callbackUrl)
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4 sm:space-y-5">
|
||||
<>
|
||||
<ClaimListsModal
|
||||
isOpen={showClaimModal}
|
||||
onClose={handleClaimClose}
|
||||
onComplete={handleClaimComplete}
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4 sm:space-y-5">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Name *
|
||||
@@ -296,5 +343,6 @@ export default function RegisterForm({ callbackUrl = '/' }: RegisterFormProps) {
|
||||
{isSubmitting ? 'Creating account...' : 'Create Account'}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user