From aff9ce7ce964c9ce72c1133afeccacc6d8cfbf47 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sun, 1 Feb 2026 19:13:14 +0100 Subject: [PATCH] Add design overhaul plan + partial icon work TODO: Comprehensive design plan for fitness app feel - Dark theme color palette - Professional typography guidelines - SVG icons to replace ALL emojis - UI component standards - Inspiration from Nike/FITBOD/Strong Partial work from Claude Code: - Icons.jsx component (SVG icons) - Dashboard.jsx updates (some emoji removal) --- frontend/src/components/Icons.jsx | 276 +++++++++++++++++++++++ frontend/src/pages/Dashboard.jsx | 62 ++--- frontend/src/pages/WorkoutSelectPage.jsx | 48 ++-- 3 files changed, 329 insertions(+), 57 deletions(-) create mode 100644 frontend/src/components/Icons.jsx diff --git a/frontend/src/components/Icons.jsx b/frontend/src/components/Icons.jsx new file mode 100644 index 0000000..bfcbb40 --- /dev/null +++ b/frontend/src/components/Icons.jsx @@ -0,0 +1,276 @@ +// Clean SVG icons for the Gravl app +// Replaces emojis with professional vector icons + +export const Icons = { + // Navigation & UI + home: ( + + + + + ), + chart: ( + + + + + + ), + user: ( + + + + + ), + logout: ( + + + + + + ), + arrowLeft: ( + + + + + ), + arrowRight: ( + + + + + ), + chevronLeft: ( + + + + ), + chevronRight: ( + + + + ), + chevronDown: ( + + + + ), + plus: ( + + + + + ), + check: ( + + + + ), + + // Workout types + dumbbell: ( + + + + + + ), + arm: ( + + + + + + + ), + leg: ( + + + + + + + ), + back: ( + + + + + + + ), + chest: ( + + + + + + ), + shoulder: ( + + + + + + + ), + core: ( + + + + + + + ), + fullBody: ( + + + + + + + + + ), + + // Activity & rest + walking: ( + + + + + + + + ), + yoga: ( + + + + + + + ), + swimming: ( + + + + + + + ), + cycling: ( + + + + + + + ), + sleep: ( + + + + ), + + // Stats & metrics + fire: ( + + + + + ), + calendar: ( + + + + + + + ), + target: ( + + + + + + ), + trophy: ( + + + + + + + + ), + + // Coach avatar (silhouette) + coach: ( + + + + + + + ), + + // Brand + gravl: ( + + + + + + ), +} + +// Icon component wrapper +export function Icon({ name, size = 24, className = '', style = {} }) { + const icon = Icons[name] + if (!icon) return null + + return ( + + {icon} + + ) +} + +// Helper to get workout icon name based on workout name +export function getWorkoutIconName(name) { + const lower = name.toLowerCase() + if (lower.includes('push') || lower.includes('bröst') || lower.includes('chest')) return 'chest' + if (lower.includes('pull') || lower.includes('rygg') || lower.includes('back')) return 'back' + if (lower.includes('ben') || lower.includes('leg') || lower.includes('lower')) return 'leg' + if (lower.includes('axlar') || lower.includes('shoulder')) return 'shoulder' + if (lower.includes('arm')) return 'arm' + if (lower.includes('core') || lower.includes('mage')) return 'core' + if (lower.includes('helkropp') || lower.includes('full')) return 'fullBody' + if (lower.includes('överkropp') || lower.includes('upper')) return 'chest' + if (lower.includes('underkropp')) return 'leg' + return 'dumbbell' +} + +// Helper to get activity icon name +export function getActivityIconName(activity) { + const lower = activity.toLowerCase() + if (lower.includes('promenad') || lower.includes('walk')) return 'walking' + if (lower.includes('yoga') || lower.includes('stretch')) return 'yoga' + if (lower.includes('sim') || lower.includes('swim')) return 'swimming' + if (lower.includes('cyk') || lower.includes('bike')) return 'cycling' + return 'walking' +} + +export default Icon diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index ea40a13..ee18d3e 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react' import { useAuth } from '../context/AuthContext' +import { Icon, getActivityIconName } from '../components/Icons' const API_URL = '/api' @@ -7,13 +8,13 @@ const API_URL = '/api' const getCoachGreeting = (user, todayWorkout) => { const hour = new Date().getHours() const name = user?.name?.split(' ')[0] || 'du' - + if (todayWorkout) { // There's a workout today if (hour < 10) { - return `Godmorgon ${name}! Idag kör vi ${todayWorkout.name.toLowerCase()}. Redo? 💪` + 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! 🏋️` + 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 { @@ -22,23 +23,23 @@ const getCoachGreeting = (user, todayWorkout) => { } else { // Rest day if (hour < 10) { - return `Godmorgon ${name}! Vilodag idag – perfekt för återhämtning. 🧘` + 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? 🚶` + 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? 🧘` + 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! 😴` + 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' }, + { iconName: 'walking', text: 'Promenad' }, + { iconName: 'yoga', text: 'Stretching' }, + { iconName: 'swimming', text: 'Simning' }, + { iconName: 'cycling', text: 'Cykling' }, ] // Get weekday names @@ -89,12 +90,12 @@ function Dashboard({ onStartWorkout, onNavigate }) {
-

🏋️ Gravl

+

Gravl

@@ -103,20 +104,20 @@ function Dashboard({ onStartWorkout, onNavigate }) { {/* Week Calendar - TOP */}
- {formatWeekRange(currentWeekStart)} -
@@ -128,14 +129,14 @@ function Dashboard({ onStartWorkout, onNavigate }) { const workout = program?.days?.find(d => d.day_number === dayNum) return ( -
hasWorkout && workout && onStartWorkout(workout)} > {name} {date.getDate()} - {hasWorkout && } + {hasWorkout && }
) })} @@ -145,7 +146,9 @@ function Dashboard({ onStartWorkout, onNavigate }) { {/* Coach Section with Today's Action */}
-
🧔‍♂️
+
+ +

{getCoachGreeting(user, todayWorkout)}

@@ -163,7 +166,7 @@ function Dashboard({ onStartWorkout, onNavigate }) {
- +
) : ( @@ -171,14 +174,17 @@ function Dashboard({ onStartWorkout, onNavigate }) {
{restDayTips.map((tip, i) => ( - {tip.icon} {tip.text} + + + {tip.text} + ))}
-
@@ -197,7 +203,7 @@ function Dashboard({ onStartWorkout, onNavigate }) { Denna vecka
- 💪 + Streak: 5
diff --git a/frontend/src/pages/WorkoutSelectPage.jsx b/frontend/src/pages/WorkoutSelectPage.jsx index f47cf21..fcd7597 100644 --- a/frontend/src/pages/WorkoutSelectPage.jsx +++ b/frontend/src/pages/WorkoutSelectPage.jsx @@ -1,32 +1,18 @@ import { useState, useEffect } from 'react' +import { Icon, getWorkoutIconName } from '../components/Icons' 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 +// Workout color based on type - more subtle/muted palette 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 + if (lower.includes('push') || lower.includes('bröst')) return 'var(--workout-push)' + if (lower.includes('pull') || lower.includes('rygg')) return 'var(--workout-pull)' + if (lower.includes('ben') || lower.includes('leg')) return 'var(--workout-legs)' + if (lower.includes('axlar')) return 'var(--workout-shoulders)' + if (lower.includes('överkropp') || lower.includes('upper')) return 'var(--workout-upper)' + if (lower.includes('underkropp') || lower.includes('lower')) return 'var(--workout-lower)' + return 'var(--workout-default)' } function WorkoutSelectPage({ onBack, onSelectWorkout }) { @@ -72,7 +58,9 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) { return (
- +

Välj pass

@@ -84,20 +72,20 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
{program?.days?.map((workout) => { - const icon = getWorkoutIcon(workout.name) + const iconName = getWorkoutIconName(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}

@@ -114,7 +102,9 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
{isSelected && ( -
+
+ +
)}
)