feature/04-workout-modification #2

Merged
sphinxen merged 14 commits from feature/04-workout-modification into main 2026-03-02 09:25:33 +01:00
2 changed files with 115 additions and 2 deletions
Showing only changes of commit f63f4c0420 - Show all commits
+50
View File
@@ -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;
}
}
+65 -2
View File
@@ -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()
} }