Fix upload auto-refresh, health data refresh, and HR zone recalculation
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 10s

- UploadPage now polls task status every 2s and invalidates activity,
  health-summary, and health-metrics queries on completion so new
  activities and health data appear without a hard refresh
- Garmin and Strava export endpoints now return a task_id for polling
- Updating max HR in Profile triggers a background Celery task to
  recalculate hr_zones for all existing activities; profile page shows
  a confirmation note when this is queued
- Add CLAUDE.md with repo architecture and dev commands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 23:13:44 +01:00
parent b5fd17a597
commit 95f704cb54
6 changed files with 205 additions and 11 deletions
+23 -5
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import api from '../utils/api'
import { useAuthStore } from '../hooks/useAuth'
@@ -59,6 +59,8 @@ export default function ProfilePage() {
// HR / measurements form
const [hrForm, setHrForm] = useState({ max_heart_rate: '', resting_heart_rate: '', birth_year: '', height_cm: '' })
const [hrSaved, setHrSaved] = useState(false)
const [hrZoneRecalc, setHrZoneRecalc] = useState(false)
const maxHrChangedRef = useRef(false)
useEffect(() => {
if (profile) setHrForm({
max_heart_rate: profile.max_heart_rate || '',
@@ -70,7 +72,16 @@ export default function ProfilePage() {
const updateProfile = useMutation({
mutationFn: data => api.patch('/profile/', data).then(r => r.data),
onSuccess: () => { qc.invalidateQueries({ queryKey: ['profile'] }); setHrSaved(true); setTimeout(() => setHrSaved(false), 3000) },
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['profile'] })
setHrSaved(true)
setTimeout(() => setHrSaved(false), 3000)
if (maxHrChangedRef.current) {
setHrZoneRecalc(true)
setTimeout(() => setHrZoneRecalc(false), 6000)
maxHrChangedRef.current = false
}
},
})
// Weight log
@@ -149,12 +160,19 @@ export default function ProfilePage() {
</div>
<SaveButton
onClick={() => updateProfile.mutate(Object.fromEntries(
Object.entries(hrForm).filter(([,v]) => v !== '').map(([k,v]) => [k, parseFloat(v)])
))}
onClick={() => {
const data = Object.fromEntries(
Object.entries(hrForm).filter(([,v]) => v !== '').map(([k,v]) => [k, parseFloat(v)])
)
maxHrChangedRef.current = data.max_heart_rate !== undefined && data.max_heart_rate !== profile?.max_heart_rate
updateProfile.mutate(data)
}}
loading={updateProfile.isPending}
saved={hrSaved}
/>
{hrZoneRecalc && (
<p className="text-xs text-blue-400 mt-1">HR zones are being recalculated for your existing activities.</p>
)}
</Section>
{/* Weight log */}