feature/04-workout-modification #2
@@ -434,3 +434,53 @@
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spinner Animation for Save Loading */
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply spinner animation to Icon component with spinner class */
|
||||
.save-header-btn svg[class*="spinner"],
|
||||
.save-header-btn .icon-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Success Checkmark Animation */
|
||||
@keyframes slideInCheckmark {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.sync-status.saved {
|
||||
animation: slideInCheckmark 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Ensure error actions align properly on mobile */
|
||||
@media (max-width: 480px) {
|
||||
.error-banner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
width: 100%;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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