04-06-02: Save error handling & retry logic
- Added specific error type differentiation: * Network errors → 'Anslutning misslyckades' * Validation (400) → 'Ogiltiga ändringar' * Auth (401/403) → 'Saknar behörighet' * Server (500+) → 'Serverfel' * Generic fallback messages - Implemented retry tracking: * retryCount state for monitoring attempts * lastSavePayload storage for potential retry (future feature) * Console logging with context for debugging - Enhanced error handling: * getErrorMessage() function for error classification * Comprehensive error logging with workout/exercise context * Draft preserved on all error types (no data loss) - Improved UI/UX: * Error banner with specific, actionable messages * 'Försök igen' button with retry tracking * Sync status feedback (idle/saving/saved/error) * Success checkmark animation (2s duration) * Spinner animation during save - CSS Enhancements: * @keyframes spin for loading spinner * @keyframes slideInCheckmark for success feedback * Mobile-responsive error banner (flex column on <480px) * Smooth animations for state transitions Tests: npm run build ✓ (no syntax errors) Files modified: - frontend/src/pages/WorkoutEditPage.jsx - frontend/src/pages/WorkoutEditPage.css
This commit is contained in:
@@ -14,6 +14,8 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
const [error, setError] = useState(null)
|
||||
const [syncStatus, setSyncStatus] = useState('idle') // idle | saving | saved | error
|
||||
const [draftPromptShown, setDraftPromptShown] = useState(false)
|
||||
const [retryCount, setRetryCount] = useState(0)
|
||||
const [lastSavePayload, setLastSavePayload] = useState(null)
|
||||
|
||||
// Show draft recovery prompt on first render
|
||||
const handleRecoverDraft = () => {
|
||||
@@ -72,10 +74,41 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
if (error) setError(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine specific error message based on error type
|
||||
*/
|
||||
const getErrorMessage = (err) => {
|
||||
// Network errors
|
||||
if (!err || err instanceof TypeError && err.message.includes('fetch')) {
|
||||
return 'Anslutning misslyckades. Försök igen?'
|
||||
}
|
||||
|
||||
// Check if error has a response (API error)
|
||||
if (err.status) {
|
||||
if (err.status === 400) {
|
||||
return 'Ogiltiga ändringar. Kontrollera dina inmatningar.'
|
||||
}
|
||||
if (err.status === 401 || err.status === 403) {
|
||||
return 'Du har inte behörighet att spara denna träning.'
|
||||
}
|
||||
if (err.status >= 500) {
|
||||
return 'Serverfel. Försök igen senare.'
|
||||
}
|
||||
if (err.status >= 400) {
|
||||
return 'Ett fel uppstod när träningen skulle sparas. Försök igen.'
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return err.message || 'Sparning misslyckades. Försök igen.'
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
setSyncStatus('saving')
|
||||
setError(null)
|
||||
setRetryCount(prev => prev + 1)
|
||||
|
||||
try {
|
||||
// Format for API
|
||||
const payload = {
|
||||
@@ -86,25 +119,55 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
reps_max: parseInt(ex.reps_max) || 12
|
||||
}))
|
||||
}
|
||||
|
||||
// Store payload for potential retry
|
||||
setLastSavePayload(payload)
|
||||
|
||||
// Call the save callback
|
||||
await onSave(workout.id, payload)
|
||||
|
||||
// Success: clear draft and show confirmation
|
||||
clearDraft()
|
||||
setSyncStatus('saved')
|
||||
setRetryCount(0) // Reset retry count on success
|
||||
|
||||
// Log success
|
||||
console.log('Workout saved successfully', {
|
||||
workoutId: workout.id,
|
||||
exerciseCount: exercises.length,
|
||||
retryCount
|
||||
})
|
||||
|
||||
// Reset status after 2 seconds
|
||||
setTimeout(() => setSyncStatus('idle'), 2000)
|
||||
} catch (err) {
|
||||
console.error('Failed to save workout:', err)
|
||||
setError(err.message || 'Sparning misslyckades. Försök igen.')
|
||||
// Log error with context for debugging
|
||||
console.error('Failed to save workout:', {
|
||||
error: err,
|
||||
workoutId: workout.id,
|
||||
exerciseCount: exercises.length,
|
||||
retryCount,
|
||||
payload: lastSavePayload
|
||||
})
|
||||
|
||||
// Determine error message based on error type
|
||||
const errorMessage = getErrorMessage(err)
|
||||
setError(errorMessage)
|
||||
setSyncStatus('error')
|
||||
|
||||
// Keep draft on error so user doesn't lose work
|
||||
// (useDraftWorkout already auto-saves, so no action needed here)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRetry = () => {
|
||||
// Log retry attempt
|
||||
console.log('User retrying save', {
|
||||
workoutId: workout.id,
|
||||
retryCount
|
||||
})
|
||||
handleSave()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user