feature/04-workout-modification #2
@@ -434,3 +434,53 @@
|
|||||||
max-width: 90%;
|
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 [error, setError] = useState(null)
|
||||||
const [syncStatus, setSyncStatus] = useState('idle') // idle | saving | saved | error
|
const [syncStatus, setSyncStatus] = useState('idle') // idle | saving | saved | error
|
||||||
const [draftPromptShown, setDraftPromptShown] = useState(false)
|
const [draftPromptShown, setDraftPromptShown] = useState(false)
|
||||||
|
const [retryCount, setRetryCount] = useState(0)
|
||||||
|
const [lastSavePayload, setLastSavePayload] = useState(null)
|
||||||
|
|
||||||
// Show draft recovery prompt on first render
|
// Show draft recovery prompt on first render
|
||||||
const handleRecoverDraft = () => {
|
const handleRecoverDraft = () => {
|
||||||
@@ -72,10 +74,41 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
|||||||
if (error) setError(null)
|
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 () => {
|
const handleSave = async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
setSyncStatus('saving')
|
setSyncStatus('saving')
|
||||||
setError(null)
|
setError(null)
|
||||||
|
setRetryCount(prev => prev + 1)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Format for API
|
// Format for API
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -86,25 +119,55 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
|||||||
reps_max: parseInt(ex.reps_max) || 12
|
reps_max: parseInt(ex.reps_max) || 12
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store payload for potential retry
|
||||||
|
setLastSavePayload(payload)
|
||||||
|
|
||||||
|
// Call the save callback
|
||||||
await onSave(workout.id, payload)
|
await onSave(workout.id, payload)
|
||||||
|
|
||||||
// Success: clear draft and show confirmation
|
// Success: clear draft and show confirmation
|
||||||
clearDraft()
|
clearDraft()
|
||||||
setSyncStatus('saved')
|
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
|
// Reset status after 2 seconds
|
||||||
setTimeout(() => setSyncStatus('idle'), 2000)
|
setTimeout(() => setSyncStatus('idle'), 2000)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save workout:', err)
|
// Log error with context for debugging
|
||||||
setError(err.message || 'Sparning misslyckades. Försök igen.')
|
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')
|
setSyncStatus('error')
|
||||||
|
|
||||||
// Keep draft on error so user doesn't lose work
|
// Keep draft on error so user doesn't lose work
|
||||||
|
// (useDraftWorkout already auto-saves, so no action needed here)
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
|
// Log retry attempt
|
||||||
|
console.log('User retrying save', {
|
||||||
|
workoutId: workout.id,
|
||||||
|
retryCount
|
||||||
|
})
|
||||||
handleSave()
|
handleSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user