├── .gitignore ├── src ├── index.css ├── Components │ ├── ScrollToTop.jsx │ ├── FitnessAdComponent.jsx │ ├── AdvancedSliderFeature.jsx │ ├── FeaturedCards.jsx │ ├── NearbyGyms.jsx │ ├── Muscles.jsx │ ├── PaymentModal.jsx │ ├── FeatureShowcase.jsx │ └── NavBar.jsx ├── main.jsx ├── Loaders │ ├── Loader.jsx │ └── LoadingContext.jsx ├── services │ └── gymService.js ├── Exercises │ ├── ExerciseList.jsx │ └── WorkoutPlan.jsx ├── App.jsx ├── App.css ├── Pages │ ├── NearbyGymsPage.jsx │ ├── BrowseExercise.jsx │ ├── DefaultPage.jsx │ ├── Plans.jsx │ ├── AllExercises.jsx │ ├── MyWorkoutPlans.jsx │ ├── FoodAnalysis.jsx │ └── WorkoutPlanDetail.jsx ├── assets │ └── react.svg └── Logins │ ├── SignIn.jsx │ └── Register.jsx ├── vercel.json ├── postcss.config.js ├── tailwind.config.js ├── vite.config.js ├── README.md ├── index.html ├── eslint.config.js ├── package.json ├── public └── vite.svg └── AI_TRAINER_README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .env/ 2 | node_modules/ 3 | .env -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | {"source": "/(.*)", "destination": "/"} 4 | ] 5 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | 13 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | base: '/', // This ensures assets are loaded correctly 8 | build: { 9 | outDir: 'dist' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/Components/ScrollToTop.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | 4 | const ScrollToTop = () => { 5 | const { pathname } = useLocation(); 6 | 7 | useEffect(() => { 8 | window.scrollTo(0, 0); 9 | }, [pathname]); 10 | 11 | return null; 12 | }; 13 | 14 | export default ScrollToTop; 15 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | import { BrowserRouter } from 'react-router-dom' 6 | import ScrollToTop from './Components/ScrollToTop' 7 | 8 | createRoot(document.getElementById('root')).render( 9 | 10 | 11 | 12 | 13 | 14 | , 15 | ) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /src/Loaders/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { motion } from 'framer-motion' 3 | 4 | const Loader = () => { 5 | return ( 6 |
7 | 22 |
23 | ) 24 | } 25 | 26 | export default Loader -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Eleweight 9 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Loaders/LoadingContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState, useEffect } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import Loader from './Loader'; 4 | const LoadingContext = createContext(); 5 | 6 | export const LoadingProvider = ({ children }) => { 7 | const [loading, setLoading] = useState(false); 8 | const location = useLocation(); 9 | 10 | useEffect(() => { 11 | setLoading(true); 12 | const timer = setTimeout(() => { 13 | setLoading(false); 14 | }, 800); 15 | 16 | return () => clearTimeout(timer); 17 | }, [location.pathname]); 18 | 19 | return ( 20 | 21 | {loading && } 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export const useLoading = () => useContext(LoadingContext); -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /src/services/gymService.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const MAPS_API_KEY = import.meta.env.VITE_MAPS_API_KEY 4 | 5 | /** 6 | * Fetch nearby gyms based on user location 7 | * @param {Object} params - Search parameters 8 | * @param {string} params.latitude - User's latitude 9 | * @param {string} params.longitude - User's longitude 10 | * @param {number} params.radius - Search radius in meters (default: 1500) 11 | * @returns {Promise} - Promise with the API response 12 | */ 13 | export const getNearbyGyms = async ({ latitude, longitude, radius = 1500 }) => { 14 | try { 15 | const response = await axios.get( 16 | `https://maps.gomaps.pro/maps/api/place/nearbysearch/json`, 17 | { 18 | params: { 19 | keyword: 'gym', 20 | location: `${latitude},${longitude}`, 21 | name: 'gym', 22 | radius, 23 | key: MAPS_API_KEY 24 | }, 25 | headers: { 26 | 'Accept': 'application/json' 27 | } 28 | } 29 | ); 30 | 31 | return response.data; 32 | } catch (error) { 33 | console.error('Error fetching nearby gyms:', error); 34 | throw error; 35 | } 36 | }; 37 | 38 | export default { 39 | getNearbyGyms 40 | }; -------------------------------------------------------------------------------- /src/Exercises/ExerciseList.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useEffect, useState } from 'react'; 3 | import { fetchExercises } from './ExerciseApi'; 4 | 5 | const ExerciseList = () => { 6 | const [exercises, setExercises] = useState([]); 7 | const [loading, setLoading] = useState(true); 8 | 9 | useEffect(() => { 10 | const getExercises = async () => { 11 | try { 12 | const exerciseData = await fetchExercises(); 13 | setExercises(exerciseData); 14 | } catch (error) { 15 | console.error('Failed to load exercises:', error); 16 | } finally { 17 | setLoading(false); 18 | } 19 | }; 20 | 21 | getExercises(); 22 | }, []); 23 | 24 | if (loading) return
Loading exercises...
; 25 | 26 | return ( 27 |
28 |

Exercise List

29 | 39 |
40 | ); 41 | }; 42 | 43 | export default ExerciseList; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@cloudinary/react": "^1.14.1", 14 | "@cloudinary/url-gen": "^1.21.0", 15 | "@flaticon/flaticon-uicons": "^3.3.1", 16 | "@google/generative-ai": "^0.21.0", 17 | "axios": "^1.7.7", 18 | "framer-motion": "^11.11.11", 19 | "lucide-react": "^0.474.0", 20 | "react": "^18.3.1", 21 | "react-dom": "^18.3.1", 22 | "react-icons": "^5.3.0", 23 | "react-markdown": "^9.0.3", 24 | "react-router-dom": "^6.27.0", 25 | "react-webcam": "^7.2.0", 26 | "rehype-raw": "^7.0.0", 27 | "remark-gfm": "^4.0.1" 28 | }, 29 | "devDependencies": { 30 | "@eslint/js": "^9.13.0", 31 | "@types/react": "^18.3.12", 32 | "@types/react-dom": "^18.3.1", 33 | "@vitejs/plugin-react": "^4.3.3", 34 | "autoprefixer": "^10.4.20", 35 | "eslint": "^9.13.0", 36 | "eslint-plugin-react": "^7.37.2", 37 | "eslint-plugin-react-hooks": "^5.0.0", 38 | "eslint-plugin-react-refresh": "^0.4.14", 39 | "globals": "^15.11.0", 40 | "postcss": "^8.4.47", 41 | "tailwindcss": "^3.4.14", 42 | "vite": "^5.4.10" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Register from './Logins/Register' 3 | import SignIn from './Logins/SignIn' 4 | import { Route , Routes } from 'react-router-dom' 5 | import Home from './Components/Home' 6 | import BrowseExercise from './Pages/BrowseExercise' 7 | import MuscleExercises from './Components/Muscles' 8 | import Plans from './Pages/Plans' 9 | import DietPlans from './Pages/DietPlans' 10 | import { LoadingProvider } from './Loaders/LoadingContext' 11 | import WorkoutPlan from './Exercises/WorkoutPlan' 12 | import AllExercises from './Pages/AllExercises' 13 | import CustomPlan from './Pages/CustomPlan' 14 | import MyWorkoutPlans from './Pages/MyWorkoutPlans' 15 | import WorkoutPlanDetail from './Pages/WorkoutPlanDetail' 16 | import EditPlan from './Pages/EditPlan' 17 | import NearbyGymsPage from './Pages/NearbyGymsPage' 18 | import FoodAnalysis from './Pages/FoodAnalysis' 19 | import AITrainerChatbot from './Components/AITrainerChatbot' 20 | 21 | const App = () => { 22 | 23 | return ( 24 |
25 | 26 | 27 | } > 28 | } > 29 | } > 30 | } > 31 | } > 32 | } > 33 | } > 34 | } /> 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | } /> 42 | } /> 43 | 44 | 45 | 46 |
47 | ) 48 | } 49 | 50 | export default App 51 | -------------------------------------------------------------------------------- /AI_TRAINER_README.md: -------------------------------------------------------------------------------- 1 | # AI Gym Trainer Chatbot 2 | 3 | ## Overview 4 | The AI Gym Trainer Chatbot is a specialized assistant that helps users with fitness, gym workouts, and healthcare-related questions. It's powered by Google's Gemini AI and is designed to provide personalized workout guidance, exercise tutorials, and equipment usage instructions. 5 | 6 | ## Features 7 | 8 | ### 1. Fitness Knowledge Base 9 | - Answers questions about workouts, exercises, and fitness routines 10 | - Provides guidance on proper form and technique 11 | - Offers diet and nutrition advice related to fitness goals 12 | - Suggests workout plans based on user goals and equipment availability 13 | 14 | ### 2. Gym Equipment Recognition 15 | - Upload or capture images of gym machines 16 | - AI identifies the equipment and provides: 17 | - Usage instructions 18 | - Target muscle groups 19 | - Exercise benefits 20 | - Proper form guidance 21 | 22 | ### 3. Restricted to Fitness Topics 23 | - The AI is programmed to only discuss topics related to fitness, gym, and healthcare 24 | - It will politely decline to answer questions outside these domains 25 | 26 | ## How to Use 27 | 28 | ### Text Queries 29 | 1. Click the dumbbell icon in the bottom-right corner of any page 30 | 2. Type your fitness-related question in the text input 31 | 3. Press Enter or click the send button 32 | 4. Receive personalized fitness guidance 33 | 34 | ### Image Analysis 35 | 1. Open the chatbot by clicking the dumbbell icon 36 | 2. Click the "Camera" button to use your device's camera or "Upload" to select an image 37 | 3. If using the camera, click "Capture" when the gym equipment is in frame 38 | 4. The AI will analyze the image and provide detailed information about the equipment 39 | 40 | ## Technical Implementation 41 | 42 | The AI Trainer Chatbot is built using: 43 | - React for the frontend UI 44 | - Google Gemini API for AI processing 45 | - React Webcam for camera functionality 46 | - Tailwind CSS for styling 47 | 48 | The chatbot is designed to be non-intrusive, appearing as a floating button that expands into a chat window when clicked. 49 | 50 | ## Privacy Note 51 | 52 | Images uploaded or captured are only used for the immediate analysis and are not stored on our servers. All processing is done via the Google Gemini API with appropriate privacy measures in place. 53 | 54 | ## Feedback and Improvements 55 | 56 | We're constantly working to improve the AI Trainer's knowledge and capabilities. If you have suggestions or encounter any issues, please let us know through the feedback form in the app settings. -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .calculator-container { 2 | max-width: 800px; 3 | margin: 0 auto; 4 | padding: 20px; 5 | font-family: Arial, sans-serif; 6 | } 7 | 8 | .calculator-title { 9 | text-align: center; 10 | color: #333; 11 | margin-bottom: 30px; 12 | } 13 | 14 | .calculator-form { 15 | margin-bottom: 30px; 16 | } 17 | 18 | .form-grid { 19 | display: grid; 20 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 21 | gap: 20px; 22 | margin-bottom: 20px; 23 | } 24 | 25 | .form-group { 26 | display: flex; 27 | flex-direction: column; 28 | } 29 | 30 | .form-group label { 31 | margin-bottom: 8px; 32 | font-weight: 500; 33 | color: #444; 34 | } 35 | 36 | .form-group input, 37 | .form-group select { 38 | padding: 10px; 39 | border: 1px solid #ddd; 40 | border-radius: 4px; 41 | font-size: 14px; 42 | transition: border-color 0.3s ease; 43 | } 44 | 45 | .form-group input:focus, 46 | .form-group select:focus { 47 | outline: none; 48 | border-color: #2196F3; 49 | } 50 | 51 | .error-message { 52 | padding: 12px; 53 | margin-bottom: 15px; 54 | background-color: #ffebee; 55 | color: #c62828; 56 | border-radius: 4px; 57 | font-size: 14px; 58 | } 59 | 60 | .submit-button { 61 | width: 100%; 62 | padding: 12px; 63 | background-color: #4CAF50; 64 | color: white; 65 | border: none; 66 | border-radius: 4px; 67 | cursor: pointer; 68 | font-size: 16px; 69 | transition: background-color 0.3s ease; 70 | } 71 | 72 | .submit-button:hover { 73 | background-color: #43A047; 74 | } 75 | 76 | .submit-button.loading { 77 | background-color: #9E9E9E; 78 | cursor: not-allowed; 79 | } 80 | 81 | .results-container { 82 | margin-top: 30px; 83 | } 84 | 85 | .metrics-grid { 86 | display: grid; 87 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 88 | gap: 20px; 89 | margin-bottom: 20px; 90 | } 91 | 92 | .metric-card { 93 | padding: 20px; 94 | background-color: #f5f5f5; 95 | border-radius: 8px; 96 | text-align: center; 97 | transition: transform 0.3s ease; 98 | } 99 | 100 | .metric-card:hover { 101 | transform: translateY(-2px); 102 | } 103 | 104 | .metric-card h3 { 105 | margin: 0 0 10px 0; 106 | color: #2196F3; 107 | font-size: 24px; 108 | } 109 | 110 | .metric-card p { 111 | margin: 0; 112 | color: #666; 113 | font-size: 14px; 114 | } 115 | 116 | .diet-plan-card { 117 | padding: 20px; 118 | background-color: #f5f5f5; 119 | border-radius: 8px; 120 | } 121 | 122 | .diet-plan-card h3 { 123 | margin: 0 0 15px 0; 124 | color: #333; 125 | } 126 | 127 | .diet-plan-card p { 128 | margin: 0; 129 | line-height: 1.6; 130 | color: #444; 131 | } 132 | 133 | /* Responsive adjustments */ 134 | @media (max-width: 600px) { 135 | .calculator-container { 136 | padding: 15px; 137 | } 138 | 139 | .form-grid { 140 | grid-template-columns: 1fr; 141 | } 142 | 143 | .metrics-grid { 144 | grid-template-columns: 1fr; 145 | } 146 | } -------------------------------------------------------------------------------- /src/Pages/NearbyGymsPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import NavBar from '../Components/NavBar'; 4 | import NearbyGyms from '../Components/NearbyGyms'; 5 | import { MapPin } from 'lucide-react'; 6 | 7 | const NearbyGymsPage = () => { 8 | const navigate = useNavigate(); 9 | const token = localStorage.getItem('token'); 10 | 11 | useEffect(() => { 12 | if (!token) { 13 | navigate("/login"); 14 | } 15 | }, [token, navigate]); 16 | 17 | return ( 18 |
19 | 20 | 21 | {/* Hero Section */} 22 |
23 |
24 |
25 | 26 |
27 |

Find Gyms Near You

28 |

29 | Discover fitness centers in your area to kickstart your fitness journey. 30 | We'll help you find the perfect gym based on your location. 31 |

32 |
33 |
34 | 35 | {/* Main Content */} 36 |
37 | 38 |
39 | 40 | {/* FAQ Section */} 41 |
42 |
43 |

Frequently Asked Questions

44 | 45 |
46 | {[ 47 | { 48 | question: "How does the gym finder work?", 49 | answer: "Our gym finder uses your current location (with your permission) to search for fitness centers near you. We display information like ratings, opening hours, and directions to help you choose the right gym." 50 | }, 51 | { 52 | question: "Why do I need to enable location services?", 53 | answer: "Location services help us find gyms that are closest to your current position. This ensures you get the most relevant results. If you prefer not to share your location, we'll use a default location instead." 54 | }, 55 | { 56 | question: "Can I adjust the search radius?", 57 | answer: "Yes! You can use the slider to adjust how far you want to search for gyms, from 500 meters up to 5 kilometers." 58 | }, 59 | { 60 | question: "Are the gym ratings accurate?", 61 | answer: "The ratings shown are based on user reviews from Google Maps. They reflect the experiences of people who have visited these gyms." 62 | } 63 | ].map((faq, index) => ( 64 |
65 |

{faq.question}

66 |

{faq.answer}

67 |
68 | ))} 69 |
70 |
71 |
72 |
73 | ); 74 | }; 75 | 76 | export default NearbyGymsPage; -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/FitnessAdComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CheckCircle, ArrowRight } from 'lucide-react'; 3 | 4 | const FitnessAdComponent = () => { 5 | return ( 6 |
7 |
8 | {/* Image Grid Section - Enhanced with overlays and better spacing */} 9 |
10 |
11 | 12 |
13 |

FITNESS PROGRAM

14 | 15 |
16 |
17 |
18 | Woman lifting weights 23 |
24 |
25 |
26 |
27 |
28 | Man working out 33 |
34 |
35 |
36 |
37 |
38 | Woman with barbell 43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 | Member 51 | Member 52 | Member 53 |
54 |

Join 2,500+ members

55 |
56 |
57 |
58 | 59 | {/* Content Section - Enhanced with better typography and spacing */} 60 |
61 | 62 |
63 |

64 | Transform your physique with our fitness plan 65 |

66 | 67 |

68 | Join our community and achieve your fitness goals with personalized workout plans and expert guidance. 69 |

70 | 71 |
72 |
73 |
74 | 75 |
76 |

Increase Muscle and Strength

77 |
78 |
79 |
80 | 81 |
82 |

Be Healthier than before

83 |
84 |
85 |
86 | 87 |
88 |

Increase Stamina

89 |
90 |
91 | 92 |
93 | 97 | 98 |
99 |
100 |
101 |
102 |
103 | ); 104 | }; 105 | 106 | export default FitnessAdComponent; -------------------------------------------------------------------------------- /src/Pages/BrowseExercise.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Link, Navigate, useLocation } from 'react-router-dom'; 3 | import { motion } from 'framer-motion'; 4 | import NavBar from '../Components/NavBar'; 5 | import { Dumbbell, Trophy, ChevronRight, Target } from 'lucide-react'; 6 | 7 | const MuscleCard = ({ to, imageSrc, title, exercises, difficulty, benefits }) => ( 8 | 14 | 15 | 19 | 20 |
21 | {`${title} 26 |
27 |
28 | 29 |
30 |
31 | Explore exercises 32 | 33 |
34 |
35 |
36 | 37 |

38 | {title} 39 |

40 | 41 | {exercises} exercises 42 | 43 |
44 | 45 |
46 |
47 | 48 | {difficulty} 49 |
50 |
51 | 52 | {benefits} 53 |
54 |
55 | 56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 | 67 | ); 68 | 69 | const BrowseExercise = () => { 70 | const location = useLocation(); 71 | const token = localStorage.getItem('token'); 72 | 73 | useEffect(() => { 74 | if (!token) { 75 | navigate("/login"); 76 | } 77 | }, [token, Navigate]); 78 | 79 | 80 | useEffect(() => { 81 | window.scrollTo(0, 0); 82 | }, [location]); 83 | 84 | const muscles = [ 85 | { 86 | id: 'chest', 87 | title: 'Chest', 88 | image: 'https://i.pinimg.com/474x/52/86/62/5286624abd2910a4f11157be790fafd9.jpg', 89 | exercises: '12', 90 | difficulty: 'Intermediate', 91 | benefits: 'Upper Body Strength' 92 | }, 93 | { 94 | id: 'back', 95 | title: 'Back', 96 | image: 'https://i.pinimg.com/474x/ad/1d/cd/ad1dcdec4554e11afd0961795b6984f1.jpg', 97 | exercises: '15', 98 | difficulty: 'Advanced', 99 | benefits: 'Posture & Power' 100 | }, 101 | { 102 | id: 'shoulders', 103 | title: 'Shoulders', 104 | image: 'https://i.pinimg.com/474x/9a/f4/ed/9af4edf7dfea3b33aa498007aaf5e173.jpg', 105 | exercises: '10', 106 | difficulty: 'All Levels', 107 | benefits: '3D Definition' 108 | }, 109 | { 110 | id: 'arms', 111 | title: 'Arms', 112 | image: 'https://i.pinimg.com/474x/94/fc/97/94fc978575c901e4e40dee99c762a923.jpg', 113 | exercises: '14', 114 | difficulty: 'Beginner', 115 | benefits: 'Muscle Growth' 116 | }, 117 | { 118 | id: 'core', 119 | title: 'Core', 120 | image: 'https://i.pinimg.com/474x/1e/cd/a9/1ecda9a944ff50c39ef0de25b6813347.jpg', 121 | exercises: '16', 122 | difficulty: 'All Levels', 123 | benefits: 'Stability & Strength' 124 | }, 125 | { 126 | id: 'legs', 127 | title: 'Legs', 128 | image: 'https://i.pinimg.com/474x/50/b7/f8/50b7f82b56a8796932911e5753328682.jpg', 129 | exercises: '18', 130 | difficulty: 'Advanced', 131 | benefits: 'Power & Size' 132 | } 133 | ]; 134 | 135 | return ( 136 |
137 | 138 | 139 |
140 | 146 |

147 | 148 | Target Your 149 | 150 | 151 | {" "}Muscle Groups 152 | 153 |

154 |

155 | Select a muscle group to discover targeted exercises and build your perfect workout 156 |

157 |
158 | 159 |
160 | {muscles.map((muscle, index) => ( 161 | 170 | ))} 171 |
172 |
173 |
174 | ); 175 | }; 176 | 177 | export default BrowseExercise; -------------------------------------------------------------------------------- /src/Components/AdvancedSliderFeature.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import { motion, AnimatePresence } from 'framer-motion'; 3 | import { ChevronLeft, ChevronRight, Play, Pause } from 'lucide-react'; 4 | 5 | const AdvancedFeatureSlider = ({ features, autoplay = true, interval = 5000 }) => { 6 | const [currentIndex, setCurrentIndex] = useState(0); 7 | const [isPlaying, setIsPlaying] = useState(autoplay); 8 | const [width, setWidth] = useState(0); 9 | const [isHovering, setIsHovering] = useState(false); 10 | 11 | const itemsToShow = width >= 1024 ? 3 : width >= 640 ? 2 : 1; 12 | const totalSlides = Math.ceil(features.length / itemsToShow); 13 | 14 | useEffect(() => { 15 | const handleResize = () => { 16 | setWidth(window.innerWidth); 17 | }; 18 | 19 | handleResize(); 20 | window.addEventListener('resize', handleResize); 21 | return () => window.removeEventListener('resize', handleResize); 22 | }, []); 23 | 24 | const nextSlide = useCallback(() => { 25 | setCurrentIndex((prevIndex) => 26 | prevIndex === totalSlides - 1 ? 0 : prevIndex + 1 27 | ); 28 | }, [totalSlides]); 29 | 30 | const prevSlide = () => { 31 | setCurrentIndex((prevIndex) => 32 | prevIndex === 0 ? totalSlides - 1 : prevIndex - 1 33 | ); 34 | }; 35 | 36 | const goToSlide = (index) => { 37 | setCurrentIndex(index); 38 | }; 39 | 40 | const toggleAutoplay = () => { 41 | setIsPlaying(!isPlaying); 42 | }; 43 | 44 | useEffect(() => { 45 | let timer; 46 | if (isPlaying && !isHovering) { 47 | timer = setInterval(nextSlide, interval); 48 | } 49 | return () => { 50 | if (timer) clearInterval(timer); 51 | }; 52 | }, [isPlaying, isHovering, nextSlide, interval]); 53 | 54 | return ( 55 |
setIsHovering(true)} 58 | onMouseLeave={() => setIsHovering(false)} 59 | > 60 | {/* Gradient Background */} 61 |
62 | 63 |
64 | {/* Navigation Controls */} 65 |
66 | 73 | 74 | 81 |
82 | 83 | {/* Slider Container */} 84 |
85 | 90 | {features.map((feature, index) => ( 91 |
= 2 ? 'sm:w-1/2' : ''} p-4`} 94 | > 95 | 101 |
102 | ))} 103 |
104 |
105 | 106 | {/* Controls and Indicators */} 107 |
108 | {/* Autoplay Toggle */} 109 | 125 | 126 | {/* Dots Indicator */} 127 |
128 | {Array.from({ length: totalSlides }).map((_, index) => ( 129 |
139 |
140 |
141 |
142 | ); 143 | }; 144 | 145 | const FeatureCard = ({ title, description, icon: Icon, link }) => { 146 | return ( 147 | 151 | {/* Decorative Elements */} 152 |
153 |
154 |
155 |
156 | 157 |
158 |
159 |
160 | 161 |
162 |

163 | {title} 164 |

165 |
166 | 167 |

168 | {description} 169 |

170 | 171 |
172 | Explore 173 | 174 |
175 |
176 | 177 | ); 178 | }; 179 | 180 | export default AdvancedFeatureSlider; -------------------------------------------------------------------------------- /src/Pages/DefaultPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { ArrowRight, Activity, Heart, Users, Target, Calendar, Star, ChevronLeft, ChevronRight } from 'lucide-react'; 4 | 5 | const DefaultPage = () => { 6 | const navigate = useNavigate(); 7 | const [isHovered, setIsHovered] = useState(false); 8 | const [currentTestimonial, setCurrentTestimonial] = useState(0); 9 | 10 | const features = [ 11 | { icon: , title: "Workout Tracking", description: "Log and monitor your exercises with ease" }, 12 | { icon: , title: "Health Metrics", description: "Track vital statistics and progress" }, 13 | { icon: , title: "Community", description: "Connect with fitness enthusiasts" }, 14 | { icon: , title: "Goal Setting", description: "Set and achieve your fitness targets" }, 15 | { icon: , title: "Smart Scheduling", description: "Plan your workouts efficiently" } 16 | ]; 17 | 18 | const testimonials = [ 19 | { name: "Karan K.", role: "Fitness Enthusiast", quote: "Eleweight has transformed my fitness journey. I love how easy it is to track my workouts and stay motivated!", rating: 5 }, 20 | { name: "Sarah M.", role: "Personal Trainer", quote: "The best fitness tracking app I've ever used. My clients love the intuitive interface!", rating: 5 }, 21 | { name: "John D.", role: "Gym Owner", quote: "Eleweight has helped my gym members stay consistent and achieve their goals faster.", rating: 4 }, 22 | { name: "Emily R.", role: "Yoga Instructor", quote: "The app's scheduling feature is a game-changer for my yoga classes.", rating: 5 } 23 | ]; 24 | 25 | const nextTestimonial = () => { 26 | setCurrentTestimonial((prev) => (prev + 1) % testimonials.length); 27 | }; 28 | 29 | const prevTestimonial = () => { 30 | setCurrentTestimonial((prev) => (prev - 1 + testimonials.length) % testimonials.length); 31 | }; 32 | 33 | useEffect(() => { 34 | const interval = setInterval(nextTestimonial, 5000); 35 | return () => clearInterval(interval); 36 | }, []); 37 | 38 | return ( 39 |
40 | {/* Hero Section */} 41 |
42 |
43 |
44 | Fitness Background 49 |
50 | 51 |
52 |

53 | Welcome to Eleweight 54 |

55 |

56 | Your ultimate fitness companion for tracking workouts, managing nutrition, and achieving your fitness goals. 57 |

58 | 59 |
60 | 69 | 75 |
76 |
77 |
78 | 79 | {/* Features Section */} 80 |
81 |
82 |

Why Choose Eleweight?

83 |
84 | {features.map((feature, index) => ( 85 |
86 |
87 | {feature.icon} 88 |
89 |

{feature.title}

90 |

{feature.description}

91 |
92 | ))} 93 |
94 |
95 |
96 | 97 | {/* Testimonials Section */} 98 |
99 |
100 |

What Our Users Say

101 |
102 | 108 |
109 |

{testimonials[currentTestimonial].quote}

110 |
111 |
112 |
113 |
114 |

{testimonials[currentTestimonial].name}

115 |

{testimonials[currentTestimonial].role}

116 |
117 |
118 |
119 | {[...Array(testimonials[currentTestimonial].rating)].map((_, i) => ( 120 | 121 | ))} 122 |
123 |
124 |
125 | 131 |
132 |
133 |
134 | 135 | {/* CTA Section */} 136 |
137 |
138 |

Ready to Start Your Fitness Journey?

139 | 145 |
146 |
147 |
148 | ); 149 | }; 150 | 151 | export default DefaultPage; -------------------------------------------------------------------------------- /src/Logins/SignIn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Link, useNavigate } from 'react-router-dom' 3 | import axios from 'axios' 4 | import { Mail, Lock, Loader2, ArrowRight } from 'lucide-react' 5 | 6 | const SignIn = () => { 7 | const [email, setEmail] = useState('') 8 | const [password, setPassword] = useState('') 9 | const [errorMessage, setErrorMessage] = useState('') 10 | const [isLoading, setIsLoading] = useState(false) 11 | const navigate = useNavigate() 12 | const token = localStorage.getItem('token'); 13 | 14 | useEffect(() => { 15 | if (token) { 16 | navigate("/home"); 17 | } 18 | }, [token, navigate]); 19 | 20 | const handleSubmit = async (e) => { 21 | e.preventDefault() 22 | setIsLoading(true) 23 | setErrorMessage('') 24 | 25 | try { 26 | const API = import.meta.env.VITE_API_URL 27 | if (!API) { 28 | throw new Error('API URL is not configured') 29 | } 30 | 31 | const response = await axios.post(`${API}/login`, { email, password }) 32 | 33 | const { token, message, user } = response.data 34 | 35 | if (message === "Login Successful") { 36 | localStorage.setItem('token', token) 37 | localStorage.setItem('userName', user.name) 38 | localStorage.setItem('userEmail', user.email) 39 | navigate("/home") 40 | } else { 41 | setErrorMessage(message || "Login failed. Please try again.") 42 | } 43 | } catch (error) { 44 | console.error("Login Error:", error) 45 | 46 | if (error.message === 'API URL is not configured') { 47 | setErrorMessage('Server configuration error. Please contact support.') 48 | } else if (error.response?.status === 401) { 49 | setErrorMessage('Invalid email or password') 50 | } else if (error.response?.data?.message) { 51 | setErrorMessage(error.response.data.message) 52 | } else { 53 | setErrorMessage('Login failed. Please check your credentials') 54 | } 55 | } finally { 56 | setIsLoading(false) 57 | } 58 | } 59 | 60 | return ( 61 |
62 | {/* Left Panel - Image */} 63 |
64 |
65 | Background 70 |
71 |
72 |

Welcome Back to Better Health

73 |

74 | Continue your fitness journey with Eleweight's personalized tracking and insights. 75 |

76 |
77 |
78 | 79 | {/* Right Panel - Form */} 80 |
81 |
82 |
83 |
84 | Eleweight Logo 89 |
90 |

Sign In to Eleweight

91 |

Enter your credentials to continue

92 |
93 | 94 |
95 |
96 | {/* Email Input */} 97 |
98 | 101 |
102 | 103 | setEmail(e.target.value)} 109 | className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500" 110 | required 111 | /> 112 |
113 |
114 | 115 | {/* Password Input */} 116 |
117 | 120 |
121 | 122 | setPassword(e.target.value)} 128 | className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500" 129 | required 130 | /> 131 |
132 |
133 |
134 | 135 | {/* Error Message */} 136 | {errorMessage && ( 137 |
138 |

{errorMessage}

139 |
140 | )} 141 | 142 | {/* Submit Button */} 143 | 160 | 161 | {/* Register Link */} 162 |
163 | Don't have an account?{' '} 164 | 165 | Register now 166 | 167 |
168 |
169 |
170 |
171 |
172 | ) 173 | } 174 | 175 | export default SignIn -------------------------------------------------------------------------------- /src/Components/FeaturedCards.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { motion } from 'framer-motion'; 3 | import { 4 | Dumbbell, 5 | List, 6 | Plus, 7 | Utensils, 8 | MapPin, 9 | MoveRight, 10 | Target, 11 | } from 'lucide-react'; 12 | import { Link } from 'react-router-dom'; 13 | 14 | const FeatureCard = ({ title, description, icon: Icon, link, isNew }) => { 15 | return ( 16 | 20 | {/* Decorative top accent bar */} 21 |
22 | 23 | {isNew && ( 24 |
25 | NEW 26 |
27 | )} 28 | 29 | {/* Hover background effect */} 30 |
31 | 32 |
33 | {/* Icon with improved styling */} 34 |
35 |
36 | 37 |
38 |
39 | 40 | {/* Title with hover effect */} 41 |

42 | {title} 43 |

44 | 45 | {/* Description */} 46 |

47 | {description} 48 |

49 | 50 | {/* Button with improved styling */} 51 | 55 | Get Started 56 | 57 | 58 |
59 | 60 | ); 61 | }; 62 | 63 | const FeaturesSection = () => { 64 | const features = [ 65 | { 66 | icon: Dumbbell, 67 | title: "Browse Exercises", 68 | description: "Explore a comprehensive library of exercises categorized by muscle groups. Find detailed instructions, animations, and tips to perfect your form.", 69 | link: "/exercises" 70 | }, 71 | { 72 | icon: List, 73 | title: "Workout Plans", 74 | description: "Access curated workout plans designed by fitness experts. From beginners to advanced athletes, find the perfect routine to achieve your goals.", 75 | link: "/plans" 76 | }, 77 | { 78 | icon: Plus, 79 | title: "Create Custom Plans", 80 | description: "Design your own personalized workout routines with our easy-to-use plan builder. Mix and match exercises to create the perfect workout for your specific needs and goals.", 81 | link: "/create-plan", 82 | isNew: true 83 | }, 84 | { 85 | icon: Utensils, 86 | title: "Diet Plans", 87 | description: "Get personalized diet plans that complement your workout routine. Our nutrition guidance helps you fuel your body properly and achieve optimal results.", 88 | link: "/diet" 89 | }, 90 | { 91 | icon: MapPin, 92 | title: "Find Nearby Gyms", 93 | description: "Discover fitness centers in your area to kickstart your fitness journey. We'll help you find the perfect gym based on your location.", 94 | link: "/nearby-gyms", 95 | isNew: true 96 | }, 97 | { 98 | icon: Target, 99 | title: "Muscle Targeting", 100 | description: "Find specific exercises that target individual muscle groups for balanced, effective workouts.", 101 | link: "/muscle" 102 | } 103 | ]; 104 | 105 | // State for slider 106 | const [currentIndex, setCurrentIndex] = React.useState(0); 107 | const sliderRef = React.useRef(null); 108 | 109 | // Number of cards to show based on screen size 110 | const getVisibleCards = () => { 111 | if (typeof window !== 'undefined') { 112 | if (window.innerWidth >= 1280) return 3; // xl screens 113 | if (window.innerWidth >= 768) return 2; // md screens 114 | return 1; // small screens 115 | } 116 | return 3; // Default for SSR 117 | }; 118 | 119 | const [visibleCards, setVisibleCards] = React.useState(3); 120 | 121 | React.useEffect(() => { 122 | const handleResize = () => { 123 | setVisibleCards(getVisibleCards()); 124 | }; 125 | 126 | handleResize(); // Set initial value 127 | window.addEventListener('resize', handleResize); 128 | return () => window.removeEventListener('resize', handleResize); 129 | }, []); 130 | 131 | const maxIndex = Math.max(0, features.length - visibleCards); 132 | 133 | const nextSlide = () => { 134 | setCurrentIndex(prev => Math.min(prev + 1, maxIndex)); 135 | }; 136 | 137 | const prevSlide = () => { 138 | setCurrentIndex(prev => Math.max(prev - 1, 0)); 139 | }; 140 | 141 | const goToSlide = (index) => { 142 | setCurrentIndex(Math.min(Math.max(0, index), maxIndex)); 143 | }; 144 | 145 | return ( 146 |
147 |
148 | {/* Slider container */} 149 |
150 |
155 | {features.map((feature, index) => ( 156 | 164 | 165 | 166 | ))} 167 |
168 |
169 | 170 | {/* Navigation buttons */} 171 |
172 | {/* Previous button */} 173 | 185 | 186 | {/* Dots navigation */} 187 |
188 | {Array.from({ length: maxIndex + 1 }).map((_, idx) => ( 189 |
201 | 202 | {/* Next button */} 203 | 215 |
216 |
217 |
218 | ); 219 | }; 220 | 221 | export default FeaturesSection; -------------------------------------------------------------------------------- /src/Components/NearbyGyms.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { getNearbyGyms } from '../services/gymService'; 3 | import { MapPin, Navigation, Clock, Star, Phone, ExternalLink, Loader2 } from 'lucide-react'; 4 | 5 | const NearbyGyms = () => { 6 | const [gyms, setGyms] = useState([]); 7 | const [loading, setLoading] = useState(false); 8 | const [error, setError] = useState(null); 9 | const [userLocation, setUserLocation] = useState(null); 10 | const [searchRadius, setSearchRadius] = useState(1500); 11 | 12 | // Get user's location 13 | useEffect(() => { 14 | const getUserLocation = () => { 15 | setLoading(true); 16 | if (navigator.geolocation) { 17 | navigator.geolocation.getCurrentPosition( 18 | (position) => { 19 | const { latitude, longitude } = position.coords; 20 | setUserLocation({ latitude, longitude }); 21 | fetchNearbyGyms(latitude, longitude, searchRadius); 22 | }, 23 | (error) => { 24 | console.error('Error getting user location:', error); 25 | setError('Unable to get your location. Please enable location services and try again.'); 26 | setLoading(false); 27 | // Use default location if user denies permission 28 | const defaultLocation = { latitude: 18.399251, longitude: 76.556786 }; 29 | setUserLocation(defaultLocation); 30 | fetchNearbyGyms(defaultLocation.latitude, defaultLocation.longitude, searchRadius); 31 | } 32 | ); 33 | } else { 34 | setError('Geolocation is not supported by your browser.'); 35 | setLoading(false); 36 | } 37 | }; 38 | 39 | getUserLocation(); 40 | }, []); 41 | 42 | // Fetch nearby gyms when radius changes 43 | useEffect(() => { 44 | if (userLocation) { 45 | fetchNearbyGyms(userLocation.latitude, userLocation.longitude, searchRadius); 46 | } 47 | }, [searchRadius, userLocation]); 48 | 49 | const fetchNearbyGyms = async (latitude, longitude, radius) => { 50 | try { 51 | setLoading(true); 52 | const data = await getNearbyGyms({ latitude, longitude, radius }); 53 | setGyms(data.results || []); 54 | setError(null); 55 | } catch (err) { 56 | setError('Failed to fetch nearby gyms. Please try again later.'); 57 | console.error('Error fetching gyms:', err); 58 | } finally { 59 | setLoading(false); 60 | } 61 | }; 62 | 63 | const handleRadiusChange = (e) => { 64 | setSearchRadius(Number(e.target.value)); 65 | }; 66 | 67 | const getDirectionsUrl = (gym) => { 68 | if (!gym.geometry || !gym.geometry.location) return '#'; 69 | const { lat, lng } = gym.geometry.location; 70 | return `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}`; 71 | }; 72 | 73 | return ( 74 |
75 |
76 |

Find Nearby Gyms

77 |

78 | Discover fitness centers in your area to start your workout journey. We use your location to find the closest gyms. 79 |

80 | 81 |
82 | 85 | 96 |
97 | 500m 98 | 5000m 99 |
100 |
101 |
102 | 103 | {loading && ( 104 |
105 | 106 | Finding gyms near you... 107 |
108 | )} 109 | 110 | {error && ( 111 |
112 |
113 |
114 | 115 | 116 | 117 |
118 |
119 |

{error}

120 |
121 |
122 |
123 | )} 124 | 125 | {!loading && !error && gyms.length === 0 && ( 126 |
127 | 128 |

No gyms found

129 |

130 | Try increasing your search radius or check back later. 131 |

132 |
133 | )} 134 | 135 |
136 | {gyms.map((gym) => ( 137 |
138 | {gym.photos && gym.photos[0] ? ( 139 |
140 | 141 |
142 | ) : ( 143 |
144 | 145 |
146 | )} 147 | 148 |
149 |
150 |

{gym.name}

151 | {gym.rating && ( 152 |
153 | 154 | {gym.rating} 155 | {gym.user_ratings_total && ( 156 | ({gym.user_ratings_total}) 157 | )} 158 |
159 | )} 160 |
161 | 162 | {gym.vicinity && ( 163 |
164 | 165 |

{gym.vicinity}

166 |
167 | )} 168 | 169 | {gym.opening_hours && ( 170 |
171 | 172 |

173 | {gym.opening_hours.open_now ? ( 174 | Open now 175 | ) : ( 176 | Closed 177 | )} 178 |

179 |
180 | )} 181 | 182 | 203 |
204 |
205 | ))} 206 |
207 |
208 | ); 209 | }; 210 | 211 | export default NearbyGyms; -------------------------------------------------------------------------------- /src/Components/Muscles.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | import { motion, AnimatePresence } from 'framer-motion'; 4 | import { X, ChevronRight, Target, PlayCircle, Info } from 'lucide-react'; 5 | import exercisesData from '../exercises.json'; 6 | import NavBar from './NavBar'; 7 | 8 | const Card = ({ index, title, muscle, gif_url, onSelect }) => { 9 | return ( 10 | 20 |
21 | {/* Background Pattern */} 22 |
23 |
24 |
25 | 26 | {/* Content */} 27 |
28 | {title} 33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |

41 | {title} 42 |

43 |
44 | 45 | {muscle} 46 |
47 |
48 | 49 |
50 | View details 51 | 52 |
53 |
54 |
55 | 56 | ); 57 | }; 58 | 59 | const FullScreenCard = ({ exercise, onClose }) => { 60 | return ( 61 | 67 | 74 |
75 | {/* Header */} 76 |
77 | 83 |
84 | 85 | {/* Content */} 86 |
87 | {/* Image Section */} 88 |
89 |
90 | {exercise.name} 95 |
96 |
97 |
98 | 99 | {/* Details Section */} 100 |
101 |
102 | {/* Title */} 103 |
104 |
105 |
106 | 107 |
108 |

{exercise.name}

109 |
110 |
111 | 112 | Target Muscle: {exercise.muscle} 113 |
114 |
115 | 116 |
117 | 118 | {/* Instructions */} 119 |
120 |

Instructions

121 |
122 |
123 |
124 | 1 125 |
126 |

{exercise.description1}

127 |
128 |
129 |
130 | 2 131 |
132 |

{exercise.description2}

133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | 141 | 142 | ); 143 | }; 144 | 145 | const MuscleExercises = () => { 146 | const { muscle } = useParams(); 147 | const [exercises, setExercises] = useState([]); 148 | const [selectedIndex, setSelectedIndex] = useState(null); 149 | 150 | useEffect(() => { 151 | if (exercisesData.exercises && exercisesData.exercises[muscle]) { 152 | setExercises(exercisesData.exercises[muscle]); 153 | } 154 | }, [muscle]); 155 | 156 | const selectedExercise = selectedIndex !== null ? exercises[selectedIndex] : null; 157 | 158 | return ( 159 |
160 | 161 | 162 | {/* Header */} 163 |
164 |
165 | 171 | {muscle.charAt(0).toUpperCase() + muscle.slice(1)} Exercises 172 | 173 |
174 |
175 | 176 | {/* Content */} 177 |
178 |
179 | {exercises.length > 0 ? ( 180 | exercises.map((exercise, index) => ( 181 | setSelectedIndex(index)} 189 | /> 190 | )) 191 | ) : ( 192 |
193 |
194 | 195 |
196 |

No exercises found for this muscle group.

197 |
198 | )} 199 |
200 |
201 | 202 | {/* Modal */} 203 | 204 | {selectedExercise && ( 205 | setSelectedIndex(null)} 208 | /> 209 | )} 210 | 211 |
212 | ); 213 | }; 214 | 215 | export default MuscleExercises; 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /src/Logins/Register.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link, useNavigate } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import { Loader2, UserPlus, Mail, Lock, ArrowRight } from 'lucide-react'; 5 | 6 | const Register = () => { 7 | const [formData, setFormData] = useState({ 8 | name: '', 9 | email: '', 10 | password: '', 11 | }); 12 | const [errorMessage, setErrorMessage] = useState(''); 13 | const [isLoading, setIsLoading] = useState(false); 14 | const navigate = useNavigate(); 15 | 16 | const handleChange = (e) => { 17 | const { name, value } = e.target; 18 | setFormData((prev) => ({ 19 | ...prev, 20 | [name]: value, 21 | })); 22 | if (errorMessage) setErrorMessage(''); 23 | }; 24 | 25 | const handleSubmit = async (e) => { 26 | e.preventDefault(); 27 | setIsLoading(true); 28 | 29 | try { 30 | const API = import.meta.env.VITE_API_URL 31 | const response = await axios.post(`${API}/register`, formData); 32 | 33 | if (response.data.message === 'You are signed in') { 34 | localStorage.setItem('token', response.data.token); 35 | localStorage.setItem('userName', response.data.user.name); 36 | localStorage.setItem('userEmail', response.data.user.email); 37 | navigate('/home'); 38 | } else { 39 | setErrorMessage(response.data.message); 40 | } 41 | } catch (error) { 42 | if (error.response?.status === 409) { 43 | setErrorMessage('Email already exists'); 44 | } else if (error.response?.data?.message) { 45 | setErrorMessage(error.response.data.message); 46 | } else { 47 | setErrorMessage('Registration failed. Please try again.'); 48 | } 49 | } finally { 50 | setIsLoading(false); 51 | } 52 | }; 53 | 54 | return ( 55 |
56 | {/* Left Panel - Image */} 57 |
58 |
59 | Background 64 |
65 |
66 |

Start Your Journey to Better Health

67 |

68 | Join thousands of users who have transformed their lives with Eleweight's personalized health tracking and insights. 69 |

70 |
71 |
72 | 73 | {/* Right Panel - Form */} 74 |
75 |
76 |
77 |
78 | Eleweight Logo 83 |
84 |

Create your account

85 |

Enter your information to get started

86 |
87 | 88 |
89 |
90 | {/* Name Input */} 91 |
92 | 95 |
96 | 97 | 107 |
108 |
109 | 110 | {/* Email Input */} 111 |
112 | 115 |
116 | 117 | 127 |
128 |
129 | 130 | {/* Password Input */} 131 |
132 | 135 |
136 | 137 | 147 |
148 |
149 |
150 | 151 | {/* Error Message */} 152 | {errorMessage && ( 153 |
154 |

{errorMessage}

155 |
156 | )} 157 | 158 | {/* Submit Button */} 159 | 176 | 177 | 178 |
179 | Already have an account?{' '} 180 | 181 | Sign in 182 | 183 |
184 |
185 |
186 |
187 |
188 | ); 189 | }; 190 | 191 | export default Register; -------------------------------------------------------------------------------- /src/Pages/Plans.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Navigate, useLocation, useNavigate } from 'react-router-dom'; 3 | import { motion } from 'framer-motion'; 4 | import NavBar from '../Components/NavBar'; 5 | import { exercises } from '../exercises.json'; 6 | import { Calendar, Users, Dumbbell, Timer, ChevronRight, Target, Trophy } from 'lucide-react'; 7 | 8 | const Plans = () => { 9 | const location = useLocation(); 10 | const navigate = useNavigate(); 11 | const token = localStorage.getItem('token'); 12 | 13 | useEffect(() => { 14 | if (!token) { 15 | navigate("/login"); 16 | } 17 | }, [token, Navigate]); 18 | 19 | 20 | useEffect(() => { 21 | window.scrollTo(0, 0); 22 | }, [location]); 23 | 24 | 25 | const planDetails = { 26 | classic: { 27 | image:"https://i.pinimg.com/474x/53/ec/b5/53ecb5dae265e12b423ed8cdbdb1de03.jpg", 28 | title: 'Classic Body Part Split', 29 | days: '5 Days per Week', 30 | description: 'Traditional bodybuilding split targeting each muscle group once per week', 31 | intensity: 'High', 32 | experience: 'Intermediate' 33 | }, 34 | ppl: { 35 | image:'https://i.pinimg.com/474x/81/3a/a7/813aa7255685f11be23fd13de9ac84b2.jpg', 36 | title: 'Push, Pull, Legs', 37 | days: '6 Days per Week', 38 | description: 'Efficient split for maximum muscle growth and strength gains', 39 | intensity: 'High', 40 | experience: 'Advanced' 41 | }, 42 | 'upper-lower': { 43 | image:'https://i.pinimg.com/474x/45/40/c1/4540c1ae44d7ec684bac54292b99bdc5.jpg', 44 | title: 'Upper/Lower Split', 45 | days: '4 Days per Week', 46 | description: 'Balanced approach for building strength and muscle mass', 47 | intensity: 'Moderate', 48 | experience: 'Intermediate' 49 | }, 50 | fullbody: { 51 | image:'https://i.pinimg.com/474x/d0/ca/72/d0ca72fac05f6b51c4e888ba0c154381.jpg', 52 | title: 'Full-Body Routine', 53 | days: '3 Days per Week', 54 | description: 'Perfect for beginners and those with limited time for exercise, this routine offers a simple yet effective way to stay active.', 55 | intensity: 'Moderate', 56 | experience: 'Beginner' 57 | }, 58 | power: { 59 | image:'https://i.pinimg.com/736x/b2/15/63/b21563f95b6b34fce85bc843699038f5.jpg', 60 | title: 'Power & Hypertrophy Split', 61 | days: '5 Days per Week', 62 | description: 'Focus on building strength and increasing muscle size through a well-structured workout plan that incorporates progressive overload.', 63 | intensity: 'Very High', 64 | experience: 'Advanced' 65 | }, 66 | endurance: { 67 | image:'https://i.pinimg.com/474x/cb/5d/ee/cb5deee7a729b262409de28df8392fa4.jpg', 68 | title: 'Endurance Training', 69 | days: '4 Days per Week', 70 | description: 'Improve stamina and functional fitness along with muscle tone and endurance. This plan is perfect for those looking to increase cardiovascular health.', 71 | intensity: 'Moderate', 72 | experience: 'All Levels' 73 | } 74 | }; 75 | 76 | const handlePlanSelection = (plan) => { 77 | let selectedExercises = []; 78 | 79 | switch (plan) { 80 | case 'classic': 81 | selectedExercises = { 82 | day1: [...exercises.chest.slice(0, 4), ...exercises.arms.slice(5, 9)], // Chest & arms 83 | day2: [...exercises.back.slice(0, 4), ...exercises.arms.slice(0, 3)], // Back & arms 84 | day3: [...exercises.legs.slice(0, 6)], // Legs 85 | day4: [...exercises.shoulders.slice(0, 4), ...exercises.core.slice(0, 3)], // Shoulders & Core 86 | day5: [...exercises.arms.slice(0, 4), ...exercises.core.slice(0, 3)] // Arms & Core 87 | }; 88 | break; 89 | 90 | case 'ppl': 91 | selectedExercises = { 92 | day1: [...exercises.chest.slice(0, 4), ...exercises.shoulders.slice(0, 3)], // Push 93 | day2: [...exercises.back.slice(0, 4), ...exercises.arms.slice(0, 3)], // Pull 94 | day3: [...exercises.legs.slice(0, 6)], // Legs 95 | day4: [...exercises.chest.slice(0, 4), ...exercises.shoulders.slice(0, 3)], // Push 96 | day5: [...exercises.back.slice(0, 4), ...exercises.arms.slice(0, 3)], // Pull 97 | day6: [...exercises.legs.slice(0, 6)], // Legs 98 | }; 99 | break; 100 | 101 | case 'upper-lower': 102 | selectedExercises = { 103 | day1: [...exercises.chest.slice(0, 3), ...exercises.back.slice(0, 3), ...exercises.shoulders.slice(0, 3)], 104 | day2: [...exercises.legs.slice(0, 6)], 105 | day3: [...exercises.chest.slice(0, 3), ...exercises.back.slice(0, 3), ...exercises.shoulders.slice(0, 3)], 106 | day4: [...exercises.legs.slice(0, 6)] 107 | }; 108 | break; 109 | 110 | case 'fullbody': 111 | selectedExercises = { 112 | day1: [...exercises.chest.slice(0, 2), ...exercises.back.slice(0, 2), ...exercises.legs.slice(0, 2), ...exercises.shoulders.slice(0, 2), ...exercises.arms.slice(0, 2)], 113 | day2: [...exercises.chest.slice(0, 2), ...exercises.back.slice(0, 2), ...exercises.legs.slice(0, 2), ...exercises.shoulders.slice(0, 2), ...exercises.core.slice(0, 2)], 114 | day3: [...exercises.chest.slice(0, 2), ...exercises.back.slice(0, 2), ...exercises.legs.slice(0, 2), ...exercises.shoulders.slice(0, 2), ...exercises.arms.slice(0, 2)] 115 | }; 116 | break; 117 | 118 | case 'power': 119 | selectedExercises = { 120 | day1: [...exercises.chest.slice(0, 3), ...exercises.arms.slice(0, 3)], 121 | day2: [...exercises.back.slice(0, 3), ...exercises.arms.slice(0, 3)], 122 | day3: [...exercises.legs.slice(0, 6)], 123 | day4: [...exercises.shoulders.slice(0, 3), ...exercises.core.slice(0, 3)], 124 | day5: [...exercises.arms.slice(0, 4), ...exercises.core.slice(0, 3)] 125 | }; 126 | break; 127 | 128 | case 'endurance': 129 | selectedExercises = { 130 | day1: [...exercises.chest.slice(0, 2), ...exercises.back.slice(0, 2), ...exercises.core.slice(0, 2)], 131 | day2: [...exercises.legs.slice(0, 4), ...exercises.core.slice(0, 2)], 132 | day3: [...exercises.shoulders.slice(0, 2), ...exercises.arms.slice(0, 2), ...exercises.core.slice(0, 2)], 133 | day4: [...exercises.chest.slice(0, 2), ...exercises.back.slice(0, 2), ...exercises.core.slice(0, 2)] 134 | }; 135 | break; 136 | 137 | default: 138 | selectedExercises = {}; 139 | } 140 | 141 | 142 | navigate(`/plans/${plan}`, { state: { exercises: selectedExercises } }); 143 | }; 144 | 145 | return ( 146 |
147 | 148 | 149 |
150 | 156 |

157 | 158 | Choose Your 159 | 160 | {" "} 161 | Training Plan 162 | 163 | 164 |

165 |

166 | Select a workout plan that matches your goals and experience level 167 |

168 |
169 | 170 |
171 | {Object.keys(planDetails).map((plan, index) => ( 172 | handlePlanSelection(plan)} 178 | className="overflow-hidden relative bg-white rounded-2xl shadow-md transition-all duration-300 cursor-pointer group hover:shadow-xl" 179 | > 180 |
181 | {planDetails[plan].title} 186 |
187 |
188 | 189 |
190 |

191 | {planDetails[plan].title} 192 |

193 | 194 |

195 | {planDetails[plan].description} 196 |

197 | 198 |
199 |
200 | 201 | {planDetails[plan].days} 202 |
203 |
204 | 205 | {planDetails[plan].intensity} 206 |
207 |
208 | 209 | {planDetails[plan].experience} 210 |
211 |
212 | 213 | Strength Focus 214 |
215 |
216 | 217 |
218 |
219 | 220 | ))} 221 |
222 |
223 |
224 | ); 225 | }; 226 | 227 | export default Plans; -------------------------------------------------------------------------------- /src/Components/PaymentModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { motion, AnimatePresence } from 'framer-motion'; 3 | import { X, CreditCard, Check, Lock, AlertCircle } from 'lucide-react'; 4 | 5 | const PaymentModal = ({ isOpen, onClose, plan, price, period }) => { 6 | const [step, setStep] = useState(1); 7 | const [cardNumber, setCardNumber] = useState(''); 8 | const [cardName, setCardName] = useState(''); 9 | const [expiryDate, setExpiryDate] = useState(''); 10 | const [cvv, setCvv] = useState(''); 11 | const [isProcessing, setIsProcessing] = useState(false); 12 | const [isSuccess, setIsSuccess] = useState(false); 13 | const [error, setError] = useState(''); 14 | 15 | const handleSubmit = (e) => { 16 | e.preventDefault(); 17 | 18 | // Basic validation 19 | if (!cardNumber || !cardName || !expiryDate || !cvv) { 20 | setError('Please fill in all fields'); 21 | return; 22 | } 23 | 24 | if (cardNumber.replace(/\s/g, '').length !== 16) { 25 | setError('Card number must be 16 digits'); 26 | return; 27 | } 28 | 29 | if (cvv.length < 3) { 30 | setError('CVV must be at least 3 digits'); 31 | return; 32 | } 33 | 34 | // Clear any previous errors 35 | setError(''); 36 | 37 | // Simulate payment processing 38 | setIsProcessing(true); 39 | 40 | setTimeout(() => { 41 | setIsProcessing(false); 42 | setIsSuccess(true); 43 | 44 | // Close modal after showing success message 45 | setTimeout(() => { 46 | onClose(); 47 | setStep(1); 48 | setIsSuccess(false); 49 | // Reset form 50 | setCardNumber(''); 51 | setCardName(''); 52 | setExpiryDate(''); 53 | setCvv(''); 54 | }, 3000); 55 | }, 2000); 56 | }; 57 | 58 | const formatCardNumber = (value) => { 59 | const v = value.replace(/\s+/g, '').replace(/[^0-9]/gi, ''); 60 | const matches = v.match(/\d{4,16}/g); 61 | const match = (matches && matches[0]) || ''; 62 | const parts = []; 63 | 64 | for (let i = 0, len = match.length; i < len; i += 4) { 65 | parts.push(match.substring(i, i + 4)); 66 | } 67 | 68 | if (parts.length) { 69 | return parts.join(' '); 70 | } else { 71 | return value; 72 | } 73 | }; 74 | 75 | const handleCardNumberChange = (e) => { 76 | const formattedValue = formatCardNumber(e.target.value); 77 | setCardNumber(formattedValue); 78 | }; 79 | 80 | const handleExpiryDateChange = (e) => { 81 | let value = e.target.value.replace(/\D/g, ''); 82 | 83 | if (value.length > 2) { 84 | value = value.substring(0, 2) + '/' + value.substring(2, 4); 85 | } 86 | 87 | setExpiryDate(value); 88 | }; 89 | 90 | return ( 91 | 92 | {isOpen && ( 93 | 99 | 105 | 111 | 112 | {/* Header */} 113 |
114 |

115 | {isSuccess ? 'Payment Successful!' : `Subscribe to ${plan}`} 116 |

117 |

118 | {isSuccess 119 | ? 'Thank you for your purchase!' 120 | : `${price} billed ${period === 'month' ? 'monthly' : 'annually'}`} 121 |

122 |
123 | 124 |
125 | {isSuccess ? ( 126 |
127 |
128 | 129 |
130 |

Payment Successful!

131 |

132 | Your subscription has been activated successfully. Enjoy your premium features! 133 |

134 |
135 | ) : step === 1 ? ( 136 |
137 |
138 |
139 |
140 | 1 141 |
142 | Payment Details 143 |
144 |
145 | 146 | {error && ( 147 |
148 | 149 |

{error}

150 |
151 | )} 152 | 153 |
154 |
155 | 158 |
159 | 168 | 169 |
170 |
171 | 172 |
173 | 176 | setCardName(e.target.value)} 181 | placeholder="John Doe" 182 | className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" 183 | /> 184 |
185 | 186 |
187 |
188 | 191 | 200 |
201 |
202 | 205 | setCvv(e.target.value.replace(/\D/g, '').substring(0, 4))} 210 | placeholder="123" 211 | maxLength="4" 212 | className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" 213 | /> 214 |
215 |
216 | 217 |
218 | 219 | Your payment information is secure and encrypted 220 |
221 | 222 | 239 |
240 |
241 | ) : null} 242 |
243 |
244 |
245 | )} 246 |
247 | ); 248 | }; 249 | 250 | export default PaymentModal; -------------------------------------------------------------------------------- /src/Components/FeatureShowcase.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { useState, useEffect, useCallback, useRef } from 'react'; 4 | import { motion, AnimatePresence } from 'framer-motion'; 5 | import { ChevronLeft, ChevronRight, PlayCircle, PauseCircle } from 'lucide-react'; 6 | 7 | const FeatureShowcase = ({ features = [] }) => { 8 | const [currentIndex, setCurrentIndex] = useState(0); 9 | const [isPlaying, setIsPlaying] = useState(true); 10 | const [isHovering, setIsHovering] = useState(false); 11 | 12 | // Early return if no features 13 | if (!features || features.length === 0) { 14 | return null; 15 | } 16 | 17 | const nextSlide = useRef(() => {}); 18 | 19 | nextSlide.current = useCallback(() => { 20 | setCurrentIndex((prevIndex) => 21 | prevIndex === features.length - 1 ? 0 : prevIndex + 1 22 | ); 23 | }, [features.length]); 24 | 25 | const prevSlide = () => { 26 | setCurrentIndex((prevIndex) => 27 | prevIndex === 0 ? features.length - 1 : prevIndex - 1 28 | ); 29 | }; 30 | 31 | const goToSlide = (index) => { 32 | setCurrentIndex(index); 33 | }; 34 | 35 | const toggleAutoplay = () => { 36 | setIsPlaying(!isPlaying); 37 | }; 38 | 39 | useEffect(() => { 40 | let timer; 41 | if (isPlaying && !isHovering) { 42 | timer = setInterval(() => nextSlide.current(), 5000); 43 | } 44 | return () => { 45 | if (timer) clearInterval(timer); 46 | }; 47 | }, [isPlaying, isHovering]); 48 | 49 | return ( 50 |
setIsHovering(true)} 53 | onMouseLeave={() => setIsHovering(false)} 54 | > 55 | {/* Progress bar */} 56 |
57 | 69 |
70 | 71 | {/* Main Slider */} 72 |
73 | 74 | {features[currentIndex] && ( 75 | 83 | {/* Image with overlay */} 84 |
85 | 93 |
94 |
95 | 96 | {/* Content overlay */} 97 |
98 | 104 |
105 | {features[currentIndex].icon && ( 106 | 112 | {React.createElement(features[currentIndex].icon, { 113 | className: "w-6 h-6 text-white" 114 | })} 115 | 116 | )} 117 | {features[currentIndex].isNew && ( 118 | 124 | New 125 | 126 | )} 127 |
128 | 129 | 135 | {features[currentIndex].title} 136 | 137 | 138 | 144 | {features[currentIndex].description} 145 | 146 | 147 | {features[currentIndex].link && ( 148 | 153 | 157 | Learn More 158 | 163 | → 164 | 165 | 166 | 167 | )} 168 |
169 |
170 | 171 | )} 172 | 173 | 174 | {/* Navigation arrows - more subtle and positioned better */} 175 |
176 | 185 | Previous slide 186 | 187 | 188 | 189 | 198 | Next slide 199 | 200 | 201 |
202 | 203 | {/* Controls footer - redesigned */} 204 |
205 | {/* Play/Pause button */} 206 | 215 | {isPlaying ? ( 216 | 217 | ) : ( 218 | 219 | )} 220 | 221 | {isPlaying ? "Pause" : "Play"} 222 | 223 | 224 | 225 | {/* Dots navigation - enhanced */} 226 |
227 | {features.map((_, index) => ( 228 | goToSlide(index)} 231 | className={`h-1.5 sm:h-2 rounded-full transition-all ${ 232 | currentIndex === index 233 | ? 'w-6 sm:w-8 bg-white shadow-glow' 234 | : 'w-1.5 sm:w-2 bg-white/30 hover:bg-white/60' 235 | }`} 236 | whileHover={{ scale: 1.2 }} 237 | whileTap={{ scale: 0.9 }} 238 | initial={{ opacity: 0, y: 10 }} 239 | animate={{ opacity: 1, y: 0 }} 240 | transition={{ delay: 0.7 + index * 0.05 }} 241 | aria-label={`Go to slide ${index + 1}`} 242 | /> 243 | ))} 244 |
245 | 246 | {/* Slide counter - redesigned */} 247 | 253 | {currentIndex + 1} / {features.length} 254 | 255 |
256 |
257 | 258 | {/* Add subtle particle effect */} 259 |
260 | {[...Array(20)].map((_, i) => ( 261 | 278 | ))} 279 |
280 | 281 | {/* Add custom styles */} 282 | 287 |
288 | ); 289 | }; 290 | 291 | FeatureShowcase.defaultProps = { 292 | features: [] 293 | }; 294 | 295 | export default FeatureShowcase; 296 | -------------------------------------------------------------------------------- /src/Pages/AllExercises.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { motion } from 'framer-motion'; 4 | import NavBar from '../Components/NavBar'; 5 | import { exercises } from '../exercises.json'; 6 | import { Search, Filter, X, ChevronDown, ChevronUp, Dumbbell, Info, Plus } from 'lucide-react'; 7 | 8 | const AllExercises = () => { 9 | const navigate = useNavigate(); 10 | const token = localStorage.getItem('token'); 11 | const [searchTerm, setSearchTerm] = useState(''); 12 | const [selectedMuscle, setSelectedMuscle] = useState(''); 13 | const [isFilterOpen, setIsFilterOpen] = useState(false); 14 | const [allExercises, setAllExercises] = useState([]); 15 | const [selectedExercise, setSelectedExercise] = useState(null); 16 | 17 | useEffect(() => { 18 | if (!token) { 19 | navigate("/login"); 20 | } 21 | }, [token, navigate]); 22 | 23 | useEffect(() => { 24 | // Flatten the exercises object into an array 25 | const exerciseArray = []; 26 | Object.entries(exercises).forEach(([muscleGroup, exerciseList]) => { 27 | exerciseList.forEach(exercise => { 28 | exerciseArray.push({ 29 | ...exercise, 30 | muscleGroup 31 | }); 32 | }); 33 | }); 34 | setAllExercises(exerciseArray); 35 | }, []); 36 | 37 | const muscleGroups = Object.keys(exercises); 38 | 39 | const filteredExercises = allExercises.filter(exercise => { 40 | const matchesSearch = exercise.name.toLowerCase().includes(searchTerm.toLowerCase()); 41 | const matchesMuscle = selectedMuscle ? exercise.muscleGroup === selectedMuscle : true; 42 | return matchesSearch && matchesMuscle; 43 | }); 44 | 45 | return ( 46 |
47 | 48 | 49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 | setSearchTerm(e.target.value)} 62 | /> 63 |
64 | 65 |
66 | 80 | 81 | {isFilterOpen && ( 82 |
83 |
84 | 93 | {muscleGroups.map((muscle) => ( 94 | 104 | ))} 105 |
106 |
107 | )} 108 |
109 |
110 | 111 | {selectedMuscle && ( 112 |
113 | Filtered by: 114 | 115 | {selectedMuscle.charAt(0).toUpperCase() + selectedMuscle.slice(1)} 116 | 122 | 123 |
124 | )} 125 |
126 | 127 |
128 |

129 | {filteredExercises.length} {filteredExercises.length === 1 ? 'Exercise' : 'Exercises'} Found 130 |

131 |

132 | Click on any exercise to view details and add it to your custom workout plan 133 |

134 |
135 | 136 |
137 | {filteredExercises.map((exercise, index) => ( 138 | setSelectedExercise(exercise)} 143 | /> 144 | ))} 145 |
146 | 147 | {filteredExercises.length === 0 && ( 148 |
149 | 150 |

No exercises found matching your criteria.

151 | 160 |
161 | )} 162 |
163 | 164 | {/* Exercise Detail Modal */} 165 | {selectedExercise && ( 166 |
167 | 173 |
174 | 180 | 181 |
182 |
183 |
184 | {selectedExercise.name} 189 |
190 |
191 | 192 |
193 |
194 |
195 |
196 |
197 | 198 |
199 |

{selectedExercise.name}

200 |
201 |
202 | 203 | {selectedExercise.muscleGroup.charAt(0).toUpperCase() + selectedExercise.muscleGroup.slice(1)} 204 | 205 | 206 | {selectedExercise.muscle} 207 |
208 |
209 | 210 |
211 |

Instructions

212 |
213 |

{selectedExercise.description1}

214 |

{selectedExercise.description2}

215 |
216 |
217 | 218 |
219 | 229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 | )} 237 |
238 | ); 239 | }; 240 | 241 | const ExerciseCard = ({ exercise, index, onClick }) => { 242 | return ( 243 | 247 |
248 |
249 | {exercise.name} 254 |
255 |
256 | 257 |
258 |
259 | 260 |
261 |
262 |

{exercise.name}

263 | 264 | {exercise.muscleGroup.charAt(0).toUpperCase() + exercise.muscleGroup.slice(1)} 265 | 266 |
267 |

268 | {exercise.description1} 269 |

270 |
271 | 272 | {exercise.muscle} 273 | 274 | View details 275 |
276 |
277 |
278 |
279 | ); 280 | }; 281 | 282 | export default AllExercises; -------------------------------------------------------------------------------- /src/Exercises/WorkoutPlan.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import { motion, AnimatePresence } from 'framer-motion'; 4 | import { Calendar, Target, ChevronRight, Info, PlayCircle, X, ArrowLeft, Dumbbell, Clock } from 'lucide-react'; 5 | import NavBar from '../Components/NavBar'; 6 | 7 | const FullScreenCard = ({ exercise, onClose }) => { 8 | return ( 9 | 15 | 21 |
22 | 28 | 29 |
30 |
31 |
32 | {exercise.name} 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 | 47 |
48 |

{exercise.name}

49 |
50 |
51 |
52 | 53 | Target Muscle: {exercise.muscle} 54 |
55 | {exercise.sets && exercise.reps && ( 56 |
57 | 58 | Sets × Reps: {exercise.sets} × {exercise.reps} 59 |
60 | )} 61 |
62 |
63 | 64 |
65 | 66 | {(exercise.description1 || exercise.description2) && ( 67 |
68 |

Instructions

69 |
70 | {exercise.description1 && ( 71 |
72 |
73 | 1 74 |
75 |

{exercise.description1}

76 |
77 | )} 78 | {exercise.description2 && ( 79 |
80 |
81 | 2 82 |
83 |

{exercise.description2}

84 |
85 | )} 86 |
87 |
88 | )} 89 |
90 |
91 |
92 |
93 | 94 | 95 | ); 96 | }; 97 | 98 | const DayCard = ({ day, exerciseCount, onClick }) => { 99 | return ( 100 | 106 |
107 |
108 |
109 |
110 |
111 | 112 |
113 |
114 |

115 | {day.replace('day', 'Day ')} 116 |

117 |

118 | {exerciseCount} exercise{exerciseCount !== 1 ? 's' : ''} 119 |

120 |
121 |
122 | 123 |
124 |
125 | 126 | ); 127 | }; 128 | 129 | const ExerciseList = ({ day, exercises, onBack, onSelectExercise }) => { 130 | return ( 131 | 137 |
138 | 144 |

145 | {day.replace('day', 'Day ')} Exercises 146 |

147 |
148 | 149 |
150 | {exercises.map((exercise, index) => ( 151 | onSelectExercise(exercise)} 158 | > 159 |
160 |
161 |
162 | {exercise.name} 167 |
168 | 169 |
170 |
171 | 172 |
173 |

174 | {exercise.name} 175 |

176 |
177 |
178 | 179 | {exercise.muscle} 180 |
181 | {exercise.sets && exercise.reps && ( 182 |
183 | 184 | 185 | {exercise.sets} × {exercise.reps} 186 | 187 |
188 | )} 189 |
190 |
191 | 192 | 193 |
194 | 195 | ))} 196 |
197 |
198 | ); 199 | }; 200 | 201 | const WorkoutPlan = () => { 202 | const location = useLocation(); 203 | const { exercises } = location.state || {}; 204 | const [selectedDay, setSelectedDay] = useState(null); 205 | const [selectedExercise, setSelectedExercise] = useState(null); 206 | 207 | if (!exercises) { 208 | return ( 209 |
210 | 211 |
212 |
213 |
214 | 215 |
216 |

No workout plan selected

217 |
218 |
219 |
220 | ); 221 | } 222 | 223 | return ( 224 |
225 | 226 | 227 |
228 |
229 | 234 |

235 | Your Workout Plan 236 |

237 |

Stay consistent, stay strong

238 |
239 |
240 |
241 | 242 |
243 | 244 | {!selectedDay ? ( 245 | 252 | {Object.entries(exercises).map(([day, dayExercises]) => ( 253 | setSelectedDay(day)} 258 | /> 259 | ))} 260 | 261 | ) : ( 262 | setSelectedDay(null)} 267 | onSelectExercise={setSelectedExercise} 268 | /> 269 | )} 270 | 271 |
272 | 273 | 274 | {selectedExercise && ( 275 | setSelectedExercise(null)} 278 | /> 279 | )} 280 | 281 |
282 | ); 283 | }; 284 | 285 | export default WorkoutPlan; -------------------------------------------------------------------------------- /src/Pages/MyWorkoutPlans.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate, Link } from 'react-router-dom'; 3 | import { motion, AnimatePresence } from 'framer-motion'; 4 | import NavBar from '../Components/NavBar'; 5 | import { Calendar, ChevronRight, Plus, Trash2, Edit, AlertCircle, Dumbbell, Clock, Users, Target } from 'lucide-react'; 6 | import axios from 'axios'; 7 | 8 | const MyWorkoutPlans = () => { 9 | const navigate = useNavigate(); 10 | const token = localStorage.getItem('token'); 11 | const [workoutPlans, setWorkoutPlans] = useState([]); 12 | const [loading, setLoading] = useState(true); 13 | const [error, setError] = useState(''); 14 | const [deleteModalOpen, setDeleteModalOpen] = useState(false); 15 | const [planToDelete, setPlanToDelete] = useState(null); 16 | 17 | const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'; 18 | 19 | useEffect(() => { 20 | if (!token) { 21 | navigate("/login"); 22 | } else { 23 | fetchWorkoutPlans(); 24 | } 25 | }, [token, navigate]); 26 | 27 | const fetchWorkoutPlans = async () => { 28 | setLoading(true); 29 | try { 30 | // Ensure token is valid 31 | if (!token) { 32 | navigate("/login"); 33 | return; 34 | } 35 | 36 | const response = await axios.get(`${API_URL}/api/workout-plans`, { 37 | headers: { 38 | Authorization: `Bearer ${token}` 39 | } 40 | }); 41 | setWorkoutPlans(response.data); 42 | } catch (error) { 43 | console.error('Error fetching workout plans:', error); 44 | if (error.response?.status === 401 || error.response?.status === 403) { 45 | // Token is invalid or expired 46 | localStorage.removeItem('token'); 47 | navigate("/login"); 48 | return; 49 | } 50 | setError(error.response?.data?.message || 'Failed to load your workout plans. Please try again later.'); 51 | } finally { 52 | setLoading(false); 53 | } 54 | }; 55 | 56 | const handleDeletePlan = async () => { 57 | if (!planToDelete) return; 58 | 59 | try { 60 | // Ensure token is valid 61 | if (!token) { 62 | setError('You must be logged in to delete a workout plan'); 63 | setDeleteModalOpen(false); 64 | return; 65 | } 66 | 67 | await axios.delete(`${API_URL}/api/workout-plans/${planToDelete}`, { 68 | headers: { 69 | Authorization: `Bearer ${token}` 70 | } 71 | }); 72 | 73 | // Remove the deleted plan from state 74 | setWorkoutPlans(workoutPlans.filter(plan => plan._id !== planToDelete)); 75 | setDeleteModalOpen(false); 76 | setPlanToDelete(null); 77 | } catch (error) { 78 | console.error('Error deleting workout plan:', error); 79 | if (error.response?.status === 401 || error.response?.status === 403) { 80 | // Token is invalid or expired 81 | localStorage.removeItem('token'); 82 | navigate("/login"); 83 | return; 84 | } else if (error.response?.status === 404) { 85 | // Plan not found, might have been deleted already 86 | setWorkoutPlans(workoutPlans.filter(plan => plan._id !== planToDelete)); 87 | setDeleteModalOpen(false); 88 | setPlanToDelete(null); 89 | } else { 90 | setError(error.response?.data?.message || 'Failed to delete the workout plan. Please try again later.'); 91 | } 92 | } 93 | }; 94 | 95 | const openDeleteModal = (planId, e) => { 96 | e.stopPropagation(); 97 | setPlanToDelete(planId); 98 | setDeleteModalOpen(true); 99 | }; 100 | 101 | const navigateToCreatePlan = () => { 102 | navigate('/create-plan'); 103 | }; 104 | 105 | const getRandomGradient = () => { 106 | const gradients = [ 107 | 'from-blue-500 to-indigo-600', 108 | 'from-green-500 to-teal-600', 109 | 'from-purple-500 to-pink-600', 110 | 'from-red-500 to-orange-600', 111 | 'from-yellow-500 to-amber-600', 112 | 'from-indigo-500 to-purple-600', 113 | 'from-teal-500 to-cyan-600', 114 | 'from-pink-500 to-rose-600' 115 | ]; 116 | return gradients[Math.floor(Math.random() * gradients.length)]; 117 | }; 118 | 119 | return ( 120 |
121 | 122 | 123 | {/* Hero Section */} 124 |
125 |
126 |
127 | 133 |

My Workout Plans

134 |

135 | Manage your custom workout routines and track your fitness journey 136 |

137 | 144 |
145 |
146 |
147 | 148 |
149 | {error && ( 150 |
151 | 152 | {error} 153 |
154 | )} 155 | 156 | {loading ? ( 157 |
158 |
159 |
160 | ) : workoutPlans.length === 0 ? ( 161 | 167 |
168 |
169 | 170 |
171 |
172 |

No Workout Plans Yet

173 |

174 | Create your first custom workout plan to start tracking your fitness journey and achieving your goals. 175 |

176 | 183 |
184 | ) : ( 185 |
186 |
187 |
188 |

Your Workout Plans

189 |

You have {workoutPlans.length} custom workout {workoutPlans.length === 1 ? 'plan' : 'plans'}

190 |
191 | 198 |
199 | 200 |
201 | 202 | {workoutPlans.map((plan, index) => ( 203 | navigate(`/workout-plan/${plan._id}`)} 211 | > 212 |
213 |
214 |
215 |

{plan.name}

216 |
217 | 227 | 234 |
235 |
236 | 237 | {plan.description && ( 238 |

{plan.description}

239 | )} 240 | 241 |
242 | 243 | {plan.days.length} {plan.days.length === 1 ? 'day' : 'days'} workout 244 | 245 | 246 | {plan.days.reduce((total, day) => total + day.exercises.length, 0)} exercises 247 |
248 | 249 |
250 | {plan.days.map((day, index) => ( 251 | 252 | {day.name} 253 | 254 | ))} 255 |
256 | 257 |
258 | 259 | Created: {new Date(plan.createdAt).toLocaleDateString()} 260 | 261 |
262 | View details 263 | 264 |
265 |
266 |
267 |
268 | ))} 269 |
270 |
271 |
272 | )} 273 |
274 | 275 | {/* Delete Confirmation Modal */} 276 | 277 | {deleteModalOpen && ( 278 | 284 | 290 |
291 | 292 |

Delete Workout Plan

293 |
294 |

295 | Are you sure you want to delete this workout plan? This action cannot be undone. 296 |

297 |
298 | 307 | 313 |
314 |
315 |
316 | )} 317 |
318 |
319 | ); 320 | }; 321 | 322 | export default MyWorkoutPlans; -------------------------------------------------------------------------------- /src/Components/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from 'react'; 2 | import { Link, NavLink, useNavigate } from 'react-router-dom'; 3 | import { motion, AnimatePresence } from 'framer-motion'; 4 | import { Dumbbell, X, Menu, User, Search, Bell, ChevronDown, LogOut, Settings, Camera, Upload, Check, UserCircle, ScanLine } from 'lucide-react'; 5 | 6 | const NavBar = () => { 7 | const navigate = useNavigate(); 8 | const [profileImage, setProfileImage] = useState('https://i.pinimg.com/474x/a3/cc/fd/a3ccfd7885e6cff94ebbbe40fd9e1611.jpg'); 9 | const [isOpen, setIsOpen] = useState(false); 10 | const [scrolled, setScrolled] = useState(false); 11 | const [profileDropdownOpen, setProfileDropdownOpen] = useState(false); 12 | const [userName, setUserName] = useState(localStorage.getItem('userName') || 'User'); 13 | const [userEmail, setUserEmail] = useState(localStorage.getItem('userEmail') || 'user@example.com'); 14 | const fileInputRef = useRef(null); 15 | const dropdownRef = useRef(null); 16 | 17 | useEffect(() => { 18 | const handleStorageChange = () => { 19 | const savedPicture = localStorage.getItem('profilePicture'); 20 | if (savedPicture) { 21 | setProfileImage(savedPicture); 22 | } 23 | setUserName(localStorage.getItem('userName') || 'User'); 24 | setUserEmail(localStorage.getItem('userEmail') || 'user@example.com'); 25 | }; 26 | 27 | const handleProfileUpdate = (e) => { 28 | setProfileImage(e.detail.picture); 29 | }; 30 | 31 | const handleScroll = () => { 32 | const offset = window.scrollY; 33 | if (offset > 50) { 34 | setScrolled(true); 35 | } else { 36 | setScrolled(false); 37 | } 38 | }; 39 | 40 | const handleClickOutside = (event) => { 41 | if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 42 | setProfileDropdownOpen(false); 43 | } 44 | }; 45 | 46 | handleStorageChange(); 47 | window.addEventListener('scroll', handleScroll); 48 | window.addEventListener('storage', handleStorageChange); 49 | window.addEventListener('profilePictureUpdate', handleProfileUpdate); 50 | document.addEventListener('mousedown', handleClickOutside); 51 | 52 | return () => { 53 | window.removeEventListener('scroll', handleScroll); 54 | window.removeEventListener('storage', handleStorageChange); 55 | window.removeEventListener('profilePictureUpdate', handleProfileUpdate); 56 | document.removeEventListener('mousedown', handleClickOutside); 57 | }; 58 | }, []); 59 | 60 | const handleLogOut = () => { 61 | localStorage.removeItem('token'); 62 | localStorage.removeItem('userName'); 63 | localStorage.removeItem('userEmail'); 64 | navigate("/login"); 65 | }; 66 | 67 | const handleFileUpload = async (e) => { 68 | const file = e.target.files[0]; 69 | if (!file) return; 70 | 71 | try { 72 | // Create a local URL for the file for preview 73 | const imageUrl = URL.createObjectURL(file); 74 | 75 | // Update the UI and localStorage 76 | setProfileImage(imageUrl); 77 | localStorage.setItem('profilePicture', imageUrl); 78 | 79 | // Notify other components about the profile picture update 80 | window.dispatchEvent(new CustomEvent('profilePictureUpdate', { 81 | detail: { picture: imageUrl } 82 | })); 83 | } catch (error) { 84 | console.error('Error handling image:', error); 85 | } 86 | }; 87 | 88 | const triggerFileInput = () => { 89 | fileInputRef.current.click(); 90 | }; 91 | 92 | // Enhanced navigation links with icons 93 | const navLinks = [ 94 | { path: "/home", name: "Home", icon: "home" }, 95 | { path: "/exercises", name: "Exercises", icon: "dumbbell" }, 96 | { path: "/my-plans", name: "My Plans", icon: "calendar" }, 97 | { path: "/diet", name: "Diet Plans", icon: "utensils" }, 98 | { path: "/nearby-gyms", name: "Find Gyms", icon: "map-pin" }, 99 | ]; 100 | 101 | return ( 102 | 351 | ); 352 | }; 353 | 354 | export default NavBar; -------------------------------------------------------------------------------- /src/Pages/FoodAnalysis.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react'; 2 | import { motion, AnimatePresence } from 'framer-motion'; 3 | import { Camera, Upload, ArrowRight } from 'lucide-react'; 4 | import axios from 'axios'; 5 | import NavBar from '../Components/NavBar'; 6 | 7 | const FoodAnalysis = () => { 8 | const [imageBase64, setImageBase64] = useState(''); 9 | const [isAnalyzingImage, setIsAnalyzingImage] = useState(false); 10 | const [foodAnalysisResults, setFoodAnalysisResults] = useState(null); 11 | const [error, setError] = useState(''); 12 | const [showCamera, setShowCamera] = useState(false); 13 | const videoRef = useRef(null); 14 | const canvasRef = useRef(null); 15 | const fileInputRef = useRef(null); 16 | 17 | const gradientText = "bg-gradient-to-r from-purple-500 via-purple-500 to-sky-500 bg-clip-text text-transparent"; 18 | 19 | // Start camera stream 20 | const startCamera = async () => { 21 | setShowCamera(true); 22 | try { 23 | const stream = await navigator.mediaDevices.getUserMedia({ 24 | video: { facingMode: 'environment' } 25 | }); 26 | 27 | if (videoRef.current) { 28 | videoRef.current.srcObject = stream; 29 | } 30 | } catch (err) { 31 | console.error("Error accessing camera:", err); 32 | setError("Could not access camera. Please check permissions or try uploading an image instead."); 33 | } 34 | }; 35 | 36 | // Stop camera stream 37 | const stopCamera = () => { 38 | if (videoRef.current && videoRef.current.srcObject) { 39 | const tracks = videoRef.current.srcObject.getTracks(); 40 | tracks.forEach(track => track.stop()); 41 | videoRef.current.srcObject = null; 42 | } 43 | setShowCamera(false); 44 | }; 45 | 46 | // Capture image from camera 47 | const captureImage = () => { 48 | if (videoRef.current && canvasRef.current) { 49 | const video = videoRef.current; 50 | const canvas = canvasRef.current; 51 | const context = canvas.getContext('2d'); 52 | 53 | // Set canvas dimensions to match video 54 | canvas.width = video.videoWidth; 55 | canvas.height = video.videoHeight; 56 | 57 | // Draw video frame to canvas 58 | context.drawImage(video, 0, 0, canvas.width, canvas.height); 59 | 60 | // Convert canvas to base64 61 | const base64Data = canvas.toDataURL('image/jpeg').split(",")[1]; 62 | setImageBase64(base64Data); 63 | 64 | // Stop camera after capturing 65 | stopCamera(); 66 | } 67 | }; 68 | 69 | // Handle file upload 70 | const handleImageUpload = (event) => { 71 | const file = event.target.files[0]; 72 | const reader = new FileReader(); 73 | 74 | reader.onloadend = () => { 75 | // Remove the "data:image/jpeg;base64," part 76 | const base64Data = reader.result.split(",")[1]; 77 | setImageBase64(base64Data); 78 | }; 79 | 80 | if (file) { 81 | reader.readAsDataURL(file); 82 | } 83 | }; 84 | 85 | // Trigger file input click 86 | const triggerFileInput = () => { 87 | fileInputRef.current.click(); 88 | }; 89 | 90 | // Analyze food using Google Gemini API 91 | const analyzeFood = async () => { 92 | if (!imageBase64) { 93 | setError('Please capture or upload an image first'); 94 | return; 95 | } 96 | 97 | setIsAnalyzingImage(true); 98 | setFoodAnalysisResults(null); 99 | setError(''); 100 | 101 | const API_URL = `https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent?key=${import.meta.env.VITE_GEMINI_API_KEY}`; 102 | 103 | const requestData = { 104 | contents: [ 105 | { 106 | parts: [ 107 | { 108 | inlineData: { 109 | mimeType: "image/jpeg", 110 | data: imageBase64 111 | } 112 | }, 113 | { 114 | text: "Identify the food in this image and provide the estimated macronutrients (calories, protein, carbs, fats). Format the response as JSON with fields: foodName, calories, protein, carbs, fats." 115 | } 116 | ] 117 | } 118 | ] 119 | }; 120 | 121 | try { 122 | const response = await axios.post(API_URL, requestData, { 123 | headers: { "Content-Type": "application/json" } 124 | }); 125 | 126 | // Extract the text response from Gemini 127 | const responseText = response.data.candidates[0].content.parts[0].text; 128 | 129 | // Try to parse JSON from the response 130 | try { 131 | // Find JSON in the response text (it might be surrounded by markdown code blocks) 132 | const jsonMatch = responseText.match(/```json\n([\s\S]*?)\n```/) || 133 | responseText.match(/```\n([\s\S]*?)\n```/) || 134 | responseText.match(/{[\s\S]*?}/); 135 | 136 | let parsedData; 137 | if (jsonMatch) { 138 | parsedData = JSON.parse(jsonMatch[1] || jsonMatch[0]); 139 | } else { 140 | // If no JSON format is found, create a simple object from the text 141 | parsedData = { 142 | analysis: responseText, 143 | foodName: "Unknown", 144 | calories: 0, 145 | protein: 0, 146 | carbs: 0, 147 | fats: 0 148 | }; 149 | } 150 | 151 | setFoodAnalysisResults(parsedData); 152 | } catch (parseError) { 153 | console.error("Error parsing response:", parseError); 154 | setFoodAnalysisResults({ 155 | analysis: responseText, 156 | foodName: "Unknown", 157 | calories: 0, 158 | protein: 0, 159 | carbs: 0, 160 | fats: 0 161 | }); 162 | } 163 | } catch (error) { 164 | console.error("Error analyzing food:", error); 165 | setError('Error analyzing food image. Please try again.'); 166 | } finally { 167 | setIsAnalyzingImage(false); 168 | } 169 | }; 170 | 171 | return ( 172 |
173 | 174 |
175 | 180 |

181 | Food Analysis with AI 182 |

183 |

184 | Take a picture of your food or upload an image to get instant nutritional information 185 |

186 |
187 | 188 | 193 |
194 |
195 | {/* Camera View */} 196 | 197 | {showCamera ? ( 198 | 204 | 233 | ) : ( 234 | 239 | {imageBase64 ? ( 240 |
241 | Food 246 |
247 | setImageBase64('')} 249 | whileHover={{ scale: 1.05 }} 250 | whileTap={{ scale: 0.95 }} 251 | className="px-4 py-2 text-white bg-red-500 rounded-lg shadow-md opacity-80 hover:opacity-100" 252 | > 253 | Change Image 254 | 255 |
256 |
257 | ) : ( 258 |
259 | 265 |
266 | 267 |
268 |
269 |

Take a Photo

270 |

Use your camera to capture food

271 |
272 |
273 | 274 | 280 |
281 | 282 |
283 |
284 |

Upload Image

285 |

Select a photo from your device

286 |
287 | 294 |
295 |
296 | )} 297 | 298 | {imageBase64 && ( 299 | 306 | {isAnalyzingImage ? ( 307 | <> 308 |
309 | Analyzing... 310 | 311 | ) : ( 312 | <> 313 | Analyze Food 314 | 315 | 316 | )} 317 | 318 | )} 319 | 320 | )} 321 | 322 | 323 | {error && ( 324 |

{error}

325 | )} 326 |
327 | 328 | 329 | {foodAnalysisResults && ( 330 | 336 |

337 | {foodAnalysisResults.foodName || "Food Analysis"} 338 |

339 | 340 |
341 |
342 |

Calories

343 |

{foodAnalysisResults.calories || 0} kcal

344 |
345 |
346 |

Protein

347 |

{foodAnalysisResults.protein || 0} g

348 |
349 |
350 |

Carbs

351 |

{foodAnalysisResults.carbs || 0} g

352 |
353 |
354 |

Fats

355 |

{foodAnalysisResults.fats || 0} g

356 |
357 |
358 | 359 | {foodAnalysisResults.analysis && ( 360 |
361 |

{foodAnalysisResults.analysis}

362 |
363 | )} 364 |
365 | )} 366 |
367 |
368 | 369 |
370 |
371 | ); 372 | }; 373 | 374 | export default FoodAnalysis; -------------------------------------------------------------------------------- /src/Pages/WorkoutPlanDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useParams, useNavigate } from 'react-router-dom'; 3 | import { motion, AnimatePresence } from 'framer-motion'; 4 | import NavBar from '../Components/NavBar'; 5 | import { Calendar, ChevronLeft, ChevronRight, Clock, Dumbbell, ArrowLeft, Info, Edit, Share, Download, X } from 'lucide-react'; 6 | import axios from 'axios'; 7 | 8 | const WorkoutPlanDetail = () => { 9 | const { id } = useParams(); 10 | const navigate = useNavigate(); 11 | const token = localStorage.getItem('token'); 12 | const [plan, setPlan] = useState(null); 13 | const [loading, setLoading] = useState(true); 14 | const [error, setError] = useState(''); 15 | const [activeDay, setActiveDay] = useState(0); 16 | const [selectedExercise, setSelectedExercise] = useState(null); 17 | 18 | const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'; 19 | 20 | useEffect(() => { 21 | if (!token) { 22 | navigate("/login"); 23 | } else { 24 | fetchWorkoutPlan(); 25 | } 26 | }, [token, navigate, id]); 27 | 28 | const fetchWorkoutPlan = async () => { 29 | setLoading(true); 30 | try { 31 | // Ensure token is valid 32 | if (!token) { 33 | navigate("/login"); 34 | return; 35 | } 36 | 37 | const response = await axios.get(`${API_URL}/api/workout-plans/${id}`, { 38 | headers: { 39 | Authorization: `Bearer ${token}` 40 | } 41 | }); 42 | setPlan(response.data); 43 | } catch (error) { 44 | console.error('Error fetching workout plan:', error); 45 | if (error.response?.status === 401 || error.response?.status === 403) { 46 | // Token is invalid or expired 47 | localStorage.removeItem('token'); 48 | navigate("/login"); 49 | return; 50 | } else if (error.response?.status === 404) { 51 | setError('Workout plan not found.'); 52 | } else { 53 | setError(error.response?.data?.message || 'Failed to load the workout plan. Please try again later.'); 54 | } 55 | } finally { 56 | setLoading(false); 57 | } 58 | }; 59 | 60 | const handlePreviousDay = () => { 61 | if (activeDay > 0) { 62 | setActiveDay(activeDay - 1); 63 | } 64 | }; 65 | 66 | const handleNextDay = () => { 67 | if (plan && activeDay < plan.days.length - 1) { 68 | setActiveDay(activeDay + 1); 69 | } 70 | }; 71 | 72 | const getTotalExercises = () => { 73 | if (!plan) return 0; 74 | return plan.days.reduce((total, day) => total + day.exercises.length, 0); 75 | }; 76 | 77 | if (loading) { 78 | return ( 79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 | ); 88 | } 89 | 90 | if (error) { 91 | return ( 92 |
93 | 94 |
95 |
96 | {error} 97 |
98 | 105 |
106 |
107 | ); 108 | } 109 | 110 | if (!plan) { 111 | return ( 112 |
113 | 114 |
115 |
116 | Workout plan not found. 117 |
118 | 125 |
126 |
127 | ); 128 | } 129 | 130 | return ( 131 |
132 | 133 | 134 | {/* Hero Section */} 135 |
136 |
137 |
138 |
139 | 146 |
147 | 148 | 153 |

{plan.name}

154 | {plan.description && ( 155 |

{plan.description}

156 | )} 157 | 158 |
159 |
160 | 161 | {plan.days.length} {plan.days.length === 1 ? 'Day' : 'Days'} 162 |
163 |
164 | 165 | {getTotalExercises()} Exercises 166 |
167 |
168 | 169 | Created {new Date(plan.createdAt).toLocaleDateString()} 170 |
171 |
172 | 173 |
174 | 181 |
182 |
183 |
184 |
185 | 186 |
187 |
188 |
189 |
190 |
191 |
192 | 193 |
194 |

195 | {plan.days[activeDay].name} 196 |

197 |
198 |
199 | 210 | 211 | Day {activeDay + 1} of {plan.days.length} 212 | 213 | 224 |
225 |
226 |
227 | 228 |
229 | {plan.days[activeDay].exercises.length === 0 ? ( 230 |
231 | 232 |

No exercises for this day.

233 | 239 |
240 | ) : ( 241 |
242 | {plan.days[activeDay].exercises.map((exercise, index) => ( 243 | setSelectedExercise(exercise)} 250 | > 251 |
252 |
253 | {exercise.name} 258 |
259 |
260 |

{exercise.name}

261 |

{exercise.muscle}

262 |
263 |
264 | 265 | {exercise.sets} sets 266 |
267 |
268 | 269 | {exercise.reps} reps 270 |
271 |
272 |
273 |
274 | 275 |
276 |
277 |
278 | ))} 279 |
280 | )} 281 |
282 |
283 | 284 |
285 |

All Days

286 |
287 | 288 |
289 | {plan.days.map((day, index) => ( 290 |
setActiveDay(index)} 298 | > 299 |
300 |

{day.name}

301 | 302 | {day.exercises.length} {day.exercises.length === 1 ? 'exercise' : 'exercises'} 303 | 304 |
305 | {day.exercises.length > 0 && ( 306 |
307 | {day.exercises.slice(0, 3).map((exercise, i) => ( 308 |
309 | 310 | {exercise.name} 311 |
312 | ))} 313 | {day.exercises.length > 3 && ( 314 |
315 | +{day.exercises.length - 3} more exercises 316 |
317 | )} 318 |
319 | )} 320 |
321 | ))} 322 |
323 |
324 | 325 | {/* Exercise Detail Modal */} 326 | 327 | {selectedExercise && ( 328 | 334 | 340 |
341 | 347 | 348 |
349 |
350 |
351 | {selectedExercise.name} 356 |
357 |
358 | 359 |
360 |
361 |
362 |
363 |
364 | 365 |
366 |

{selectedExercise.name}

367 |
368 |

{selectedExercise.muscle}

369 |
370 | 371 |
372 |
373 |
Sets
374 |
{selectedExercise.sets}
375 |
376 |
377 |
Reps
378 |
{selectedExercise.reps}
379 |
380 |
381 | 382 |
383 |

Instructions

384 |
385 |

{selectedExercise.description1}

386 |

{selectedExercise.description2}

387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 | )} 396 |
397 |
398 | ); 399 | }; 400 | 401 | export default WorkoutPlanDetail; --------------------------------------------------------------------------------