2026-07-27 08:36:31 +02:00
import { useEffect , useState } from "react" ;
2026-07-17 18:33:06 +02:00
import { api } from "../api/client" ;
2026-07-27 08:36:31 +02:00
import type { AuthResponse } from "../types/api" ;
import { SyncModal } from "./SyncModal" ;
2026-07-17 18:33:06 +02:00
2026-07-21 18:33:49 +02:00
export function GarminConnection ( { onBeforeConnect } : { onBeforeConnect ? : ( ) = > Promise < void > } ) {
2026-07-17 18:33:06 +02:00
const [ auth , setAuth ] = useState < AuthResponse | null > ( null ) ;
const [ code , setCode ] = useState ( "" ) ;
const [ busy , setBusy ] = useState ( false ) ;
const [ error , setError ] = useState < string | null > ( null ) ;
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
// There's no real Garmin logout -- the backend session just sits idle.
// "Disconnect" only hides that and shows Connect again locally; a real
// login (connect()) clears it.
const [ disconnected , setDisconnected ] = useState ( false ) ;
2026-07-27 08:36:31 +02:00
const [ showSyncModal , setShowSyncModal ] = useState ( false ) ;
2026-07-17 18:33:06 +02:00
2026-07-27 08:36:31 +02:00
// Auth status alone -- sync status is polled by SyncModal itself while
// it's open, so this no longer needs the "poll faster while syncing"
// dynamic interval it used to have.
2026-07-17 18:33:06 +02:00
useEffect ( ( ) = > {
2026-07-27 08:36:31 +02:00
api . authStatus ( ) . then ( setAuth ) . catch ( ( e ) = > setError ( String ( e ) ) ) ;
const interval = setInterval ( ( ) = > {
api . authStatus ( ) . then ( setAuth ) . catch ( ( ) = > { } ) ;
} , 6000 ) ;
return ( ) = > clearInterval ( interval ) ;
2026-07-17 18:33:06 +02:00
} , [ ] ) ;
async function connect() {
setBusy ( true ) ;
setError ( null ) ;
try {
2026-07-21 18:33:49 +02:00
await onBeforeConnect ? . ( ) ;
2026-07-17 18:33:06 +02:00
setAuth ( await api . login ( ) ) ;
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
setDisconnected ( false ) ;
2026-07-17 18:33:06 +02:00
} catch ( e ) {
setError ( String ( e ) ) ;
} finally {
setBusy ( false ) ;
}
}
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
function disconnect() {
setDisconnected ( true ) ;
}
2026-07-17 18:33:06 +02:00
async function submitMFA() {
if ( ! code . trim ( ) ) return ;
setBusy ( true ) ;
setError ( null ) ;
try {
setAuth ( await api . submitMFA ( code . trim ( ) ) ) ;
setCode ( "" ) ;
} catch ( e ) {
setError ( String ( e ) ) ;
} finally {
setBusy ( false ) ;
}
}
2026-07-19 12:41:38 +02:00
async function sync() {
2026-07-17 18:33:06 +02:00
setBusy ( true ) ;
setError ( null ) ;
try {
2026-07-19 12:41:38 +02:00
await api . syncRun ( ) ;
2026-07-27 08:36:31 +02:00
setShowSyncModal ( true ) ;
2026-07-19 12:41:38 +02:00
} catch ( e ) {
setError ( String ( e ) ) ;
} finally {
setBusy ( false ) ;
}
}
async function resetAll() {
if ( ! window . confirm ( "This deletes every synced activity, lap, and kind assignment, then starts a fresh pull from Garmin next sync. This cannot be undone. Continue?" ) ) {
return ;
}
setBusy ( true ) ;
setError ( null ) ;
try {
await api . resetSync ( ) ;
2026-07-19 12:45:03 +02:00
// Reset runs as a background sync job; wait for it to actually finish
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
// before reloading, otherwise other pages (e.g. Activities) would
2026-07-19 12:45:03 +02:00
// still show the just-deleted activities from their own stale state.
let status = await api . syncStatus ( ) ;
while ( status . in_progress ) {
await new Promise ( ( resolve ) = > setTimeout ( resolve , 300 ) ) ;
status = await api . syncStatus ( ) ;
}
window . location . reload ( ) ;
2026-07-17 18:33:06 +02:00
} catch ( e ) {
setError ( String ( e ) ) ;
setBusy ( false ) ;
}
}
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
const status = disconnected ? "unknown" : auth ? . status ? ? "unknown" ;
2026-07-17 18:33:06 +02:00
return (
< div className = "garmin-connection" >
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
< div className = "garmin-connection-row garmin-connection-main" >
< div className = "garmin-connection-actions" >
{ status !== "authenticated" && status !== "mfa_required" && (
< button disabled = { busy } onClick = { connect } >
Connect to Garmin
< / button >
) }
{ status === "authenticated" && (
< >
< button disabled = { busy } onClick = { sync } >
Sync now
< / button >
< button disabled = { busy } onClick = { disconnect } >
Disconnect
< / button >
< / >
) }
{ / * R e s e t a l l o n l y w i p e s l o c a l D B s t a t e ( s e e h a n d l e S y n c R e s e t ) - - i t
doesn ' t touch the Garmin session , so it stays available even
while disconnected / not - yet - connected . * / }
< button className = "button-danger" disabled = { busy } onClick = { resetAll } >
Reset all
2026-07-17 18:33:06 +02:00
< / button >
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
< / div >
2026-07-17 18:33:06 +02:00
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
< div className = "garmin-connection-status" >
< span className = { ` status-dot status- ${ status } ` } / >
{ / * C o n n e c t e d / n o t - c o n n e c t e d i s a l r e a d y c o n v e y e d b y t h e
Connect / Disconnect button itself -- only MFA / failed need a
label , since no button distinguishes those from "not connected" . * / }
{ ( status === "mfa_required" || status === "failed" ) && (
< span className = "status-label" >
{ status === "mfa_required" ? "MFA code required" : "Connection failed" }
< / span >
) }
< / div >
2026-07-17 18:33:06 +02:00
< / div >
{ status === "mfa_required" && (
< div className = "garmin-connection-row" >
< input
placeholder = "MFA code"
value = { code }
onChange = { ( e ) = > setCode ( e . target . value ) }
onKeyDown = { ( e ) = > e . key === "Enter" && submitMFA ( ) }
/ >
< button disabled = { busy } onClick = { submitMFA } >
Submit code
< / button >
< / div >
) }
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
{ ! disconnected && auth ? . message && < p className = "garmin-connection-message" > { auth . message } < / p > }
2026-07-17 18:33:06 +02:00
{ error && < p className = "error" > { error } < / p > }
2026-07-27 08:36:31 +02:00
{ showSyncModal && < SyncModal onClose = { ( ) = > setShowSyncModal ( false ) } / > }
2026-07-17 18:33:06 +02:00
< / div >
) ;
}