diff --git a/frontend/src/App.css b/frontend/src/App.css index 44eeacf..003ec2c 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1502,3 +1502,258 @@ background: var(--accent); color: white; } + +/* ============================================ + DASHBOARD COACH SECTION (redesigned) + ============================================ */ + +.coach-section { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.today-action { + /* Container for workout card or rest tips */ +} + +.today-workout-card { + display: flex; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, var(--accent) 0%, #6366f1 100%); + border-radius: 16px; + padding: 1.25rem; + cursor: pointer; + transition: all 0.2s; + color: white; +} + +.today-workout-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3); +} + +.workout-info h3 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 0.25rem; +} + +.workout-meta { + font-size: 0.85rem; + opacity: 0.9; +} + +.workout-action { + background: rgba(255,255,255,0.2); + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.action-arrow { + font-size: 1.5rem; +} + +/* Rest Day Section */ +.rest-day-section { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.rest-tips { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.tip-badge { + background: var(--bg-secondary); + border: 1px solid var(--border); + padding: 0.5rem 0.75rem; + border-radius: 20px; + font-size: 0.85rem; +} + +.add-workout-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + background: var(--bg-secondary); + border: 2px dashed var(--border); + border-radius: 16px; + padding: 1.25rem; + cursor: pointer; + color: var(--text-muted); + transition: all 0.2s; + font-size: 1rem; +} + +.add-workout-btn:hover { + border-color: var(--accent); + color: var(--accent); + background: var(--bg); +} + +.add-icon { + font-size: 1.5rem; + font-weight: 300; +} + +/* ============================================ + WORKOUT SELECT PAGE + ============================================ */ + +.select-page { + min-height: 100vh; + background: var(--bg); +} + +.select-page.loading { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1rem; +} + +.select-main { + padding: 1rem; + max-width: 600px; + margin: 0 auto; +} + +.select-intro { + text-align: center; + color: var(--text-muted); + margin-bottom: 1.5rem; + font-size: 1rem; +} + +.workout-grid { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.workout-select-card { + display: flex; + align-items: center; + gap: 1rem; + background: var(--bg-secondary); + border: 2px solid var(--border); + border-radius: 16px; + padding: 1rem; + cursor: pointer; + transition: all 0.2s; + position: relative; +} + +.workout-select-card:hover { + border-color: var(--workout-color, var(--accent)); + transform: translateX(4px); +} + +.workout-select-card.selected { + border-color: var(--workout-color, var(--accent)); + background: var(--bg); +} + +.workout-icon { + width: 56px; + height: 56px; + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + flex-shrink: 0; +} + +.workout-details { + flex: 1; +} + +.workout-details h3 { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.25rem; +} + +.workout-exercises-count { + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 0.5rem; +} + +.workout-preview { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; +} + +.preview-exercise { + font-size: 0.75rem; + color: var(--text-muted); + background: var(--bg); + padding: 0.2rem 0.5rem; + border-radius: 4px; +} + +.preview-more { + font-size: 0.75rem; + color: var(--accent); +} + +.selected-indicator { + position: absolute; + top: -8px; + right: -8px; + width: 28px; + height: 28px; + background: var(--workout-color, var(--accent)); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.select-action { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 1rem; + background: var(--bg); + border-top: 1px solid var(--border); +} + +.start-btn { + width: 100%; + max-width: 600px; + margin: 0 auto; + display: block; + padding: 1rem; + background: var(--accent); + color: white; + border: none; + border-radius: 12px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} + +.start-btn:hover { + background: var(--accent-hover); + transform: scale(1.02); +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index fa9ec32..c787795 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ import Dashboard from './pages/Dashboard' import ProfilePage from './pages/ProfilePage' import ProgressPage from './pages/ProgressPage' import WorkoutPage from './pages/WorkoutPage' +import WorkoutSelectPage from './pages/WorkoutSelectPage' import './App.css' const API_URL = '/api' @@ -116,6 +117,16 @@ function App() { return setView('dashboard')} /> } + // Workout select page + if (view === 'select-workout') { + return ( + setView('dashboard')} + onSelectWorkout={startWorkout} + /> + ) + } + // Workout view if (view === 'workout' && selectedDay) { return ( diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 166ef69..ea40a13 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -3,30 +3,44 @@ import { useAuth } from '../context/AuthContext' const API_URL = '/api' -// Coach greetings based on time and context +// Coach greetings based on context const getCoachGreeting = (user, todayWorkout) => { const hour = new Date().getHours() const name = user?.name?.split(' ')[0] || 'du' - if (hour < 10) { - return todayWorkout - ? `Godmorgon ${name}! đŸ’Ș Redo för ${todayWorkout.name.toLowerCase()}?` - : `Godmorgon ${name}! Vilodag idag – Ă„terhĂ€mtning Ă€r ocksĂ„ trĂ€ning.` - } else if (hour < 14) { - return todayWorkout - ? `Dags att köra ${name}! ${todayWorkout.name} vĂ€ntar.` - : `Lugn dag idag ${name}. Ladda batterierna! 🔋` - } else if (hour < 18) { - return todayWorkout - ? `Eftermiddagspass? ${todayWorkout.name} stĂ„r pĂ„ schemat đŸ‹ïž` - : `Vila upp dig ${name}. Imorgon kör vi igen!` + if (todayWorkout) { + // There's a workout today + if (hour < 10) { + return `Godmorgon ${name}! Idag kör vi ${todayWorkout.name.toLowerCase()}. Redo? đŸ’Ș` + } else if (hour < 14) { + return `${todayWorkout.name} stĂ„r pĂ„ schemat idag. Dags att köra! đŸ‹ïž` + } else if (hour < 18) { + return `Eftermiddagspass? ${todayWorkout.name} vĂ€ntar pĂ„ dig.` + } else { + return `KvĂ€llspass ${name}? ${todayWorkout.name} – perfekt för att avsluta dagen.` + } } else { - return todayWorkout - ? `KvĂ€llspass ${name}? Perfekt för att slĂ€ppa dagen.` - : `Bra jobbat denna veckan! Vila gott. 😮` + // Rest day + if (hour < 10) { + return `Godmorgon ${name}! Vilodag idag – perfekt för Ă„terhĂ€mtning. 🧘` + } else if (hour < 14) { + return `Ingen trĂ€ning schemalagd. Ta en promenad eller stretcha lite? đŸš¶` + } else if (hour < 18) { + return `Vila Ă€r ocksĂ„ trĂ€ning! LĂ€tt rörelse eller mobilitet idag? 🧘` + } else { + return `Lugn kvĂ€ll ${name}. Ladda batterierna till nĂ€sta pass! 😮` + } } } +// Rest day tips +const restDayTips = [ + { icon: 'đŸš¶', text: '30 min promenad' }, + { icon: '🧘', text: 'Yoga/stretching' }, + { icon: '🏊', text: 'Simning' }, + { icon: '🚮', text: 'LĂ€tt cykling' }, +] + // Get weekday names const weekdays = ['MĂ„n', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', 'Sön'] @@ -43,16 +57,13 @@ function Dashboard({ onStartWorkout, onNavigate }) { const fetchData = async () => { try { - // Fetch user's program const res = await fetch(`${API_URL}/programs/1`) const data = await res.json() setProgram(data) // Determine today's workout based on day of week - const dayOfWeek = new Date().getDay() // 0 = Sunday - const adjustedDay = dayOfWeek === 0 ? 7 : dayOfWeek // Convert to 1-7 (Mon-Sun) - - // Find if there's a workout scheduled for today + const dayOfWeek = new Date().getDay() + const adjustedDay = dayOfWeek === 0 ? 7 : dayOfWeek const todayDay = data.days?.find(d => d.day_number === adjustedDay) setTodayWorkout(todayDay || null) @@ -89,15 +100,7 @@ function Dashboard({ onStartWorkout, onNavigate }) {
- {/* Coach Greeting */} -
-
đŸ§”â€â™‚ïž
-
-

{getCoachGreeting(user, todayWorkout)}

-
-
- - {/* Week Calendar */} + {/* Week Calendar - TOP */}
- {/* Today's Workout */} -
-

{todayWorkout ? 'Dagens pass' : 'VĂ€lj pass'}

- {todayWorkout ? ( -
onStartWorkout(todayWorkout)}> -
-

{todayWorkout.name}

- ~45 min -
-
- {todayWorkout.exercises?.filter(e => e.name).map((ex, i) => ( -
- {ex.name} - {ex.sets}×{ex.reps_min}-{ex.reps_max} -
- ))} -
- + {/* Coach Section with Today's Action */} +
+
+
đŸ§”â€â™‚ïž
+
+

{getCoachGreeting(user, todayWorkout)}

- ) : ( -
- {program?.days?.map((workout) => ( -
onStartWorkout(workout)} - > -
-

{workout.name}

- Dag {workout.day_number} -
-
- {workout.exercises?.filter(e => e.name).slice(0, 3).map((ex, i) => ( - {ex.name} - ))} - {workout.exercises?.filter(e => e.name).length > 3 && ( - +{workout.exercises.filter(e => e.name).length - 3} - )} -
+
+ + {/* Today's Action */} +
+ {todayWorkout ? ( + // Workout today - show workout card +
onStartWorkout(todayWorkout)}> +
+

{todayWorkout.name}

+ + {todayWorkout.exercises?.filter(e => e.name).length} övningar ‱ ~45 min +
- ))} -
- )} +
+ → +
+
+ ) : ( + // Rest day - show tips + add button +
+
+ {restDayTips.map((tip, i) => ( + {tip.icon} {tip.text} + ))} +
+ +
+ )} +
{/* Quick Stats */} @@ -201,24 +201,6 @@ function Dashboard({ onStartWorkout, onNavigate }) { Streak: 5
- - {/* Upcoming Workouts */} -
-

Kommande pass

-
- {program?.days?.slice(0, 3).map((day, idx) => ( -
onStartWorkout(day)} - > - {weekdays[day.day_number - 1]} - {day.name} - → -
- ))} -
-
) diff --git a/frontend/src/pages/WorkoutSelectPage.jsx b/frontend/src/pages/WorkoutSelectPage.jsx new file mode 100644 index 0000000..f47cf21 --- /dev/null +++ b/frontend/src/pages/WorkoutSelectPage.jsx @@ -0,0 +1,137 @@ +import { useState, useEffect } from 'react' + +const API_URL = '/api' + +// Workout icons based on type/name +const getWorkoutIcon = (name) => { + const lower = name.toLowerCase() + if (lower.includes('push') || lower.includes('bröst') || lower.includes('chest')) return 'đŸ’Ș' + if (lower.includes('pull') || lower.includes('rygg') || lower.includes('back')) return 'đŸ‹ïž' + if (lower.includes('ben') || lower.includes('leg') || lower.includes('lower')) return 'đŸŠ”' + if (lower.includes('axlar') || lower.includes('shoulder')) return '🎯' + if (lower.includes('arm')) return 'đŸ’Ș' + if (lower.includes('core') || lower.includes('mage')) return 'đŸ”„' + if (lower.includes('helkropp') || lower.includes('full')) return '⚡' + if (lower.includes('överkropp') || lower.includes('upper')) return 'đŸ’Ș' + if (lower.includes('underkropp') || lower.includes('lower')) return 'đŸŠ”' + return 'đŸ‹ïž' +} + +// Workout color based on type +const getWorkoutColor = (name) => { + const lower = name.toLowerCase() + if (lower.includes('push') || lower.includes('bröst')) return '#ef4444' // Red + if (lower.includes('pull') || lower.includes('rygg')) return '#3b82f6' // Blue + if (lower.includes('ben') || lower.includes('leg')) return '#8b5cf6' // Purple + if (lower.includes('axlar')) return '#f59e0b' // Orange + if (lower.includes('överkropp') || lower.includes('upper')) return '#10b981' // Green + if (lower.includes('underkropp') || lower.includes('lower')) return '#6366f1' // Indigo + return '#6366f1' // Default indigo +} + +function WorkoutSelectPage({ onBack, onSelectWorkout }) { + const [program, setProgram] = useState(null) + const [loading, setLoading] = useState(true) + const [selectedWorkout, setSelectedWorkout] = useState(null) + + useEffect(() => { + fetchProgram() + }, []) + + const fetchProgram = async () => { + try { + const res = await fetch(`${API_URL}/programs/1`) + const data = await res.json() + setProgram(data) + setLoading(false) + } catch (err) { + console.error('Failed to fetch program:', err) + setLoading(false) + } + } + + const handleSelect = (workout) => { + setSelectedWorkout(workout) + } + + const handleStart = () => { + if (selectedWorkout) { + onSelectWorkout(selectedWorkout) + } + } + + if (loading) { + return ( +
+
+

Laddar pass...

+
+ ) + } + + return ( +
+
+ +

VĂ€lj pass

+
+
+ +
+

+ Vilken trÀning vill du köra idag? +

+ +
+ {program?.days?.map((workout) => { + const icon = getWorkoutIcon(workout.name) + const color = getWorkoutColor(workout.name) + const isSelected = selectedWorkout?.id === workout.id + const exerciseCount = workout.exercises?.filter(e => e.name).length || 0 + + return ( +
handleSelect(workout)} + > +
+ {icon} +
+
+

{workout.name}

+

+ {exerciseCount} övningar +

+
+ {workout.exercises?.filter(e => e.name).slice(0, 2).map((ex, i) => ( + {ex.name} + ))} + {exerciseCount > 2 && ( + +{exerciseCount - 2} till + )} +
+
+ {isSelected && ( +
✓
+ )} +
+ ) + })} +
+ + {/* Selected workout action */} + {selectedWorkout && ( +
+ +
+ )} +
+
+ ) +} + +export default WorkoutSelectPage