Refactor: separera user_measurements och user_strength tabeller
- Ny databasstruktur för historik/progress tracking - Nya endpoints: POST/GET measurements och strength - Onboarding sparar till rätt tabeller - Beräknar och sparar body_fat_pct - Fixar tomma numeriska fält (null istället för '') - Döljer 1RM för nybörjare
This commit is contained in:
@@ -2,6 +2,8 @@ import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
|
||||
const API = '/api';
|
||||
|
||||
const calcBodyFat = (gender, waist, neck, hip, height) => {
|
||||
if (!waist || !neck || !height) return null;
|
||||
if (gender === 'female' && !hip) return null;
|
||||
@@ -13,7 +15,7 @@ export default function OnboardingWizard() {
|
||||
const [step, setStep] = useState(1);
|
||||
const [data, setData] = useState({ gender: '', age: '', height_cm: '', weight: '', neck_cm: '', waist_cm: '', hip_cm: '', experience_level: '', bench_1rm: '', squat_1rm: '', deadlift_1rm: '', goal: '', workouts_per_week: '' });
|
||||
const [saving, setSaving] = useState(false);
|
||||
const { updateProfile } = useAuth();
|
||||
const { token, updateProfile, refreshProfile } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const update = (field, value) => setData(d => ({ ...d, [field]: value }));
|
||||
@@ -21,8 +23,52 @@ export default function OnboardingWizard() {
|
||||
|
||||
const finish = async () => {
|
||||
setSaving(true);
|
||||
await updateProfile({ ...data, onboarding_complete: true });
|
||||
navigate('/');
|
||||
try {
|
||||
// 1. Save profile
|
||||
await updateProfile({
|
||||
gender: data.gender,
|
||||
age: data.age,
|
||||
height_cm: data.height_cm,
|
||||
experience_level: data.experience_level,
|
||||
goal: data.goal,
|
||||
workouts_per_week: data.workouts_per_week,
|
||||
onboarding_complete: true
|
||||
});
|
||||
|
||||
// 2. Save measurements
|
||||
if (data.weight || data.neck_cm || data.waist_cm) {
|
||||
await fetch(`${API}/user/measurements`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
||||
body: JSON.stringify({
|
||||
weight: data.weight,
|
||||
neck_cm: data.neck_cm,
|
||||
waist_cm: data.waist_cm,
|
||||
hip_cm: data.hip_cm,
|
||||
body_fat_pct: bodyFat
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Save strength if provided
|
||||
if (data.bench_1rm || data.squat_1rm || data.deadlift_1rm) {
|
||||
await fetch(`${API}/user/strength`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
||||
body: JSON.stringify({
|
||||
bench_1rm: data.bench_1rm,
|
||||
squat_1rm: data.squat_1rm,
|
||||
deadlift_1rm: data.deadlift_1rm
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (refreshProfile) await refreshProfile();
|
||||
navigate('/');
|
||||
} catch (err) {
|
||||
console.error('Onboarding error:', err);
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -97,12 +143,16 @@ export default function OnboardingWizard() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p className="hint">1RM (valfritt)</p>
|
||||
<div className="rm-fields">
|
||||
<div className="field"><label>Bänk</label><input type="number" value={data.bench_1rm} onChange={e => update('bench_1rm', e.target.value)} placeholder="kg" /></div>
|
||||
<div className="field"><label>Knäböj</label><input type="number" value={data.squat_1rm} onChange={e => update('squat_1rm', e.target.value)} placeholder="kg" /></div>
|
||||
<div className="field"><label>Marklyft</label><input type="number" value={data.deadlift_1rm} onChange={e => update('deadlift_1rm', e.target.value)} placeholder="kg" /></div>
|
||||
</div>
|
||||
{(data.experience_level === 'intermediate' || data.experience_level === 'advanced') && (
|
||||
<>
|
||||
<p className="hint">1RM (valfritt)</p>
|
||||
<div className="rm-fields">
|
||||
<div className="field"><label>Bänk</label><input type="number" value={data.bench_1rm} onChange={e => update('bench_1rm', e.target.value)} placeholder="kg" /></div>
|
||||
<div className="field"><label>Knäböj</label><input type="number" value={data.squat_1rm} onChange={e => update('squat_1rm', e.target.value)} placeholder="kg" /></div>
|
||||
<div className="field"><label>Marklyft</label><input type="number" value={data.deadlift_1rm} onChange={e => update('deadlift_1rm', e.target.value)} placeholder="kg" /></div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="nav-btns">
|
||||
<button onClick={() => setStep(2)}>← Tillbaka</button>
|
||||
<button className="next-btn" onClick={() => setStep(4)} disabled={!data.experience_level}>Nästa →</button>
|
||||
|
||||
Reference in New Issue
Block a user