Files
gravl/frontend/src/pages/ProfilePage.jsx
T
clawd 66812f9db2 Add ProfilePage and ProgressPage
ProfilePage:
- View/edit user info (name, age, height, goal, level)
- Show current measurements (weight, body fat, waist, neck)
- Show strength records (bench/squat/deadlift 1RM)

ProgressPage:
- Tab navigation (weight, body fat, strength)
- SVG line charts for progress visualization
- Stats showing current, first, and change
- Trend indicators (up/down)

Dashboard:
- Navigation icons for profile (👤) and progress (📊)
- Connected navigation to App.jsx routing
2026-02-01 11:50:52 +01:00

300 lines
11 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react'
import { useAuth } from '../context/AuthContext'
const API_URL = '/api'
function ProfilePage({ onBack }) {
const { user, logout } = useAuth()
const [profile, setProfile] = useState(null)
const [measurements, setMeasurements] = useState(null)
const [strength, setStrength] = useState(null)
const [editing, setEditing] = useState(false)
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
// Edit form state
const [form, setForm] = useState({})
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
try {
const [profileRes, measurementsRes, strengthRes] = await Promise.all([
fetch(`${API_URL}/user/profile/${user?.id || 1}`),
fetch(`${API_URL}/user/measurements/${user?.id || 1}`),
fetch(`${API_URL}/user/strength/${user?.id || 1}`)
])
const profileData = await profileRes.json()
const measurementsData = await measurementsRes.json()
const strengthData = await strengthRes.json()
setProfile(profileData)
setMeasurements(measurementsData)
setStrength(strengthData)
setForm(profileData)
setLoading(false)
} catch (err) {
console.error('Failed to fetch profile:', err)
setLoading(false)
}
}
const handleSave = async () => {
setSaving(true)
try {
const res = await fetch(`${API_URL}/user/profile/${user?.id || 1}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form)
})
const updated = await res.json()
setProfile(updated)
setEditing(false)
} catch (err) {
console.error('Failed to save profile:', err)
}
setSaving(false)
}
const handleChange = (field, value) => {
setForm(prev => ({ ...prev, [field]: value }))
}
if (loading) {
return (
<div className="profile-page loading">
<div className="spinner"></div>
<p>Laddar profil...</p>
</div>
)
}
const latestMeasurement = measurements?.[0]
const latestStrength = strength?.[0]
return (
<div className="profile-page">
<header className="page-header">
<button className="back-btn" onClick={onBack}> Tillbaka</button>
<h1>Min profil</h1>
<button className="logout-btn" onClick={logout}></button>
</header>
<main className="page-main">
{/* Profile Info */}
<section className="profile-section">
<div className="section-header">
<h2>Personuppgifter</h2>
{!editing && (
<button className="edit-btn" onClick={() => setEditing(true)}>
Redigera
</button>
)}
</div>
{editing ? (
<div className="edit-form">
<div className="form-group">
<label>Namn</label>
<input
type="text"
value={form.name || ''}
onChange={(e) => handleChange('name', e.target.value)}
/>
</div>
<div className="form-row">
<div className="form-group">
<label>Ålder</label>
<input
type="number"
value={form.age || ''}
onChange={(e) => handleChange('age', parseInt(e.target.value))}
/>
</div>
<div className="form-group">
<label>Kön</label>
<select
value={form.gender || ''}
onChange={(e) => handleChange('gender', e.target.value)}
>
<option value="male">Man</option>
<option value="female">Kvinna</option>
</select>
</div>
</div>
<div className="form-row">
<div className="form-group">
<label>Längd (cm)</label>
<input
type="number"
value={form.height_cm || ''}
onChange={(e) => handleChange('height_cm', parseFloat(e.target.value))}
/>
</div>
<div className="form-group">
<label>Pass/vecka</label>
<input
type="number"
min="1"
max="7"
value={form.workouts_per_week || ''}
onChange={(e) => handleChange('workouts_per_week', parseInt(e.target.value))}
/>
</div>
</div>
<div className="form-group">
<label>Mål</label>
<select
value={form.goal || ''}
onChange={(e) => handleChange('goal', e.target.value)}
>
<option value="muscle">Bygga muskler</option>
<option value="strength">Öka styrka</option>
<option value="fat_loss">Fettförbränning</option>
<option value="general">Allmän fitness</option>
</select>
</div>
<div className="form-group">
<label>Erfarenhetsnivå</label>
<select
value={form.experience_level || ''}
onChange={(e) => handleChange('experience_level', e.target.value)}
>
<option value="beginner">Nybörjare</option>
<option value="intermediate">Medel</option>
<option value="advanced">Avancerad</option>
</select>
</div>
<div className="form-actions">
<button className="cancel-btn" onClick={() => { setEditing(false); setForm(profile) }}>
Avbryt
</button>
<button className="save-btn" onClick={handleSave} disabled={saving}>
{saving ? 'Sparar...' : 'Spara'}
</button>
</div>
</div>
) : (
<div className="profile-info">
<div className="info-grid">
<div className="info-item">
<span className="info-label">Namn</span>
<span className="info-value">{profile?.name || '-'}</span>
</div>
<div className="info-item">
<span className="info-label">Ålder</span>
<span className="info-value">{profile?.age || '-'} år</span>
</div>
<div className="info-item">
<span className="info-label">Längd</span>
<span className="info-value">{profile?.height_cm || '-'} cm</span>
</div>
<div className="info-item">
<span className="info-label">Kön</span>
<span className="info-value">{profile?.gender === 'male' ? 'Man' : 'Kvinna'}</span>
</div>
<div className="info-item">
<span className="info-label">Mål</span>
<span className="info-value">{getGoalLabel(profile?.goal)}</span>
</div>
<div className="info-item">
<span className="info-label">Nivå</span>
<span className="info-value">{getLevelLabel(profile?.experience_level)}</span>
</div>
<div className="info-item">
<span className="info-label">Pass/vecka</span>
<span className="info-value">{profile?.workouts_per_week || '-'}</span>
</div>
</div>
</div>
)}
</section>
{/* Current Measurements */}
<section className="profile-section">
<h2>Aktuella mätningar</h2>
{latestMeasurement ? (
<div className="measurements-grid">
<div className="measurement-card">
<span className="measurement-icon"></span>
<span className="measurement-value">{latestMeasurement.weight} kg</span>
<span className="measurement-label">Vikt</span>
</div>
{latestMeasurement.body_fat_pct && (
<div className="measurement-card">
<span className="measurement-icon">📊</span>
<span className="measurement-value">{latestMeasurement.body_fat_pct}%</span>
<span className="measurement-label">Kroppsfett</span>
</div>
)}
{latestMeasurement.waist_cm && (
<div className="measurement-card">
<span className="measurement-icon">📏</span>
<span className="measurement-value">{latestMeasurement.waist_cm} cm</span>
<span className="measurement-label">Midja</span>
</div>
)}
{latestMeasurement.neck_cm && (
<div className="measurement-card">
<span className="measurement-icon">📏</span>
<span className="measurement-value">{latestMeasurement.neck_cm} cm</span>
<span className="measurement-label">Nacke</span>
</div>
)}
</div>
) : (
<p className="no-data">Inga mätningar registrerade</p>
)}
</section>
{/* Strength Records */}
<section className="profile-section">
<h2>Styrkerekord (1RM)</h2>
{latestStrength ? (
<div className="strength-grid">
<div className="strength-card">
<span className="strength-exercise">Bänkpress</span>
<span className="strength-value">{latestStrength.bench_1rm || '-'} kg</span>
</div>
<div className="strength-card">
<span className="strength-exercise">Knäböj</span>
<span className="strength-value">{latestStrength.squat_1rm || '-'} kg</span>
</div>
<div className="strength-card">
<span className="strength-exercise">Marklyft</span>
<span className="strength-value">{latestStrength.deadlift_1rm || '-'} kg</span>
</div>
</div>
) : (
<p className="no-data">Inga styrkerekord registrerade</p>
)}
</section>
</main>
</div>
)
}
function getGoalLabel(goal) {
const labels = {
muscle: 'Bygga muskler',
strength: 'Öka styrka',
fat_loss: 'Fettförbränning',
general: 'Allmän fitness'
}
return labels[goal] || goal || '-'
}
function getLevelLabel(level) {
const labels = {
beginner: 'Nybörjare',
intermediate: 'Medel',
advanced: 'Avancerad'
}
return labels[level] || level || '-'
}
export default ProfilePage