Files
geniusrun/frontend/src/pages/Profile.tsx

153 lines
3.8 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from "react";
import { api } from "../api/client";
import type { Profile as ProfileType } from "../types/api";
function NumberField({
label,
value,
onChange,
step = 1,
}: {
label: string;
value: number;
onChange: (v: number) => void;
step?: number;
}) {
return (
<label>
{label}
<input
type="number"
step={step}
value={value}
onChange={(e) => onChange(Number(e.target.value))}
/>
</label>
);
}
function NullableNumberField({
label,
value,
onChange,
}: {
label: string;
value: number | null;
onChange: (v: number | null) => void;
}) {
return (
<label>
{label}
<input
type="number"
value={value ?? ""}
onChange={(e) => onChange(e.target.value === "" ? null : Number(e.target.value))}
/>
</label>
);
}
export function Profile() {
const [profile, setProfile] = useState<ProfileType | null>(null);
const [error, setError] = useState<string | null>(null);
const [saved, setSaved] = useState(false);
useEffect(() => {
api.getProfile().then(setProfile).catch((e) => setError(String(e)));
}, []);
function set<K extends keyof ProfileType>(key: K, value: ProfileType[K]) {
setProfile((p) => (p ? { ...p, [key]: value } : p));
setSaved(false);
}
async function save() {
if (!profile) return;
setError(null);
try {
const updated = await api.updateProfile(profile);
setProfile(updated);
setSaved(true);
} catch (e) {
setError(String(e));
setSaved(false);
}
}
if (!profile) {
return (
<div className="page">
<h2>Profile</h2>
{error ? <p className="error">{error}</p> : <p>Loading...</p>}
</div>
);
}
return (
<div className="page">
<h2>Profile</h2>
{error && <p className="error">{error}</p>}
{saved && <p className="garmin-connection-message">Saved.</p>}
<fieldset className="kind-editor">
<legend>Garmin account</legend>
<label>
Email
<input
type="text"
value={profile.GarminEmail}
onChange={(e) => set("GarminEmail", e.target.value)}
/>
</label>
<label>
Password
<input
type="password"
value={profile.GarminPassword}
onChange={(e) => set("GarminPassword", e.target.value)}
/>
</label>
</fieldset>
<fieldset className="kind-editor">
<legend>Classification</legend>
<NumberField
label="Rolling window (days)"
value={profile.RollingWindowDays}
onChange={(v) => set("RollingWindowDays", v)}
/>
</fieldset>
<fieldset className="kind-editor">
<legend>Heart rate</legend>
<NullableNumberField
label="Max heart rate (bpm)"
value={profile.MaxHeartRate}
onChange={(v) => set("MaxHeartRate", v)}
/>
<NullableNumberField
label="Resting heart rate (bpm)"
value={profile.RestingHeartRate}
onChange={(v) => set("RestingHeartRate", v)}
/>
{([1, 2, 3, 4, 5] as const).map((zone) => (
<div key={zone} className="controls">
<NumberField
label={`Zone ${zone} min %HRR`}
value={profile[`HRZone${zone}MinPct` as keyof ProfileType] as number}
onChange={(v) => set(`HRZone${zone}MinPct` as keyof ProfileType, v as never)}
/>
<NumberField
label={`Zone ${zone} max %HRR`}
value={profile[`HRZone${zone}MaxPct` as keyof ProfileType] as number}
onChange={(v) => set(`HRZone${zone}MaxPct` as keyof ProfileType, v as never)}
/>
</div>
))}
</fieldset>
<button onClick={save}>Save</button>
</div>
);
}