From 8bf6fbc49581bcff5d7bd352a548129c7e172042 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:23:01 +0000 Subject: [PATCH 1/3] Initial plan From 3c037ae9b31f6f5baa08d5938cccf188782d8991 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:30:39 +0000 Subject: [PATCH 2/3] feat: Implement configurable API base URL with localhost fallback Co-authored-by: pmca31 <6774003+pmca31@users.noreply.github.com> --- octofit-tracker/frontend/.env.example | 8 ++++- octofit-tracker/frontend/package-lock.json | 24 ++++++++++++++ .../frontend/src/components/Activities.js | 3 +- .../frontend/src/components/Leaderboard.js | 3 +- .../frontend/src/components/Teams.js | 3 +- .../frontend/src/components/Users.js | 7 ++-- .../frontend/src/components/Workouts.js | 3 +- octofit-tracker/frontend/src/config/api.js | 33 +++++++++++++++++++ 8 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 octofit-tracker/frontend/src/config/api.js diff --git a/octofit-tracker/frontend/.env.example b/octofit-tracker/frontend/.env.example index af84255..980e971 100644 --- a/octofit-tracker/frontend/.env.example +++ b/octofit-tracker/frontend/.env.example @@ -1,4 +1,10 @@ -# Set your Codespace name here +# API Configuration +# Option 1: Use a custom API base URL (takes precedence) +# REACT_APP_API_BASE_URL=http://localhost:8000 + +# Option 2: Set your Codespace name (for GitHub Codespaces) # You can get this from the CODESPACE_NAME environment variable in your terminal # Example: REACT_APP_CODESPACE_NAME=your-codespace-name REACT_APP_CODESPACE_NAME= + +# Note: If neither is set, the app will default to http://localhost:8000 diff --git a/octofit-tracker/frontend/package-lock.json b/octofit-tracker/frontend/package-lock.json index 5671bf5..5f06c7a 100644 --- a/octofit-tracker/frontend/package-lock.json +++ b/octofit-tracker/frontend/package-lock.json @@ -66,6 +66,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -715,6 +716,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1598,6 +1600,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", @@ -3360,6 +3363,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3846,6 +3850,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -3899,6 +3904,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -4268,6 +4274,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4366,6 +4373,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5294,6 +5302,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7127,6 +7136,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9864,6 +9874,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -10761,6 +10772,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -12098,6 +12110,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13232,6 +13245,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13591,6 +13605,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13722,6 +13737,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13746,6 +13762,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14224,6 +14241,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -14466,6 +14484,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15831,6 +15850,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -15999,6 +16019,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -16428,6 +16449,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.1.tgz", "integrity": "sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -16499,6 +16521,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -16912,6 +16935,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/octofit-tracker/frontend/src/components/Activities.js b/octofit-tracker/frontend/src/components/Activities.js index 172fb62..bf03eaa 100644 --- a/octofit-tracker/frontend/src/components/Activities.js +++ b/octofit-tracker/frontend/src/components/Activities.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { getApiUrl } from '../config/api'; const Activities = () => { const [activities, setActivities] = useState([]); @@ -7,7 +8,7 @@ const Activities = () => { useEffect(() => { const fetchActivities = async () => { - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/activities/`; + const apiUrl = getApiUrl('/api/activities/'); console.log('Fetching from Activities API endpoint:', apiUrl); try { diff --git a/octofit-tracker/frontend/src/components/Leaderboard.js b/octofit-tracker/frontend/src/components/Leaderboard.js index eef4ec3..8197cf2 100644 --- a/octofit-tracker/frontend/src/components/Leaderboard.js +++ b/octofit-tracker/frontend/src/components/Leaderboard.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { getApiUrl } from '../config/api'; const Leaderboard = () => { const [leaderboard, setLeaderboard] = useState([]); @@ -7,7 +8,7 @@ const Leaderboard = () => { useEffect(() => { const fetchLeaderboard = async () => { - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/leaderboard/`; + const apiUrl = getApiUrl('/api/leaderboard/'); console.log('Fetching from Leaderboard API endpoint:', apiUrl); try { diff --git a/octofit-tracker/frontend/src/components/Teams.js b/octofit-tracker/frontend/src/components/Teams.js index 73cd37a..6ae6ebe 100644 --- a/octofit-tracker/frontend/src/components/Teams.js +++ b/octofit-tracker/frontend/src/components/Teams.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { getApiUrl } from '../config/api'; const Teams = () => { const [teams, setTeams] = useState([]); @@ -7,7 +8,7 @@ const Teams = () => { useEffect(() => { const fetchTeams = async () => { - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/teams/`; + const apiUrl = getApiUrl('/api/teams/'); console.log('Fetching from Teams API endpoint:', apiUrl); try { diff --git a/octofit-tracker/frontend/src/components/Users.js b/octofit-tracker/frontend/src/components/Users.js index e7bf009..a6f5ab6 100644 --- a/octofit-tracker/frontend/src/components/Users.js +++ b/octofit-tracker/frontend/src/components/Users.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; +import { getApiUrl } from '../config/api'; const Users = () => { const [users, setUsers] = useState([]); @@ -10,7 +11,7 @@ const Users = () => { const [saveMessage, setSaveMessage] = useState(''); const fetchUsers = useCallback(async () => { - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/users/`; + const apiUrl = getApiUrl('/api/users/'); console.log('Fetching from Users API endpoint:', apiUrl); try { @@ -33,7 +34,7 @@ const Users = () => { }, []); const fetchTeams = useCallback(async () => { - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/teams/`; + const apiUrl = getApiUrl('/api/teams/'); console.log('Fetching teams for dropdown:', apiUrl); try { @@ -81,7 +82,7 @@ const Users = () => { const handleSave = async () => { if (!editingUser) return; - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/users/${editingUser.id}/`; + const apiUrl = getApiUrl(`/api/users/${editingUser.id}/`); console.log('Updating user:', apiUrl, formData); try { diff --git a/octofit-tracker/frontend/src/components/Workouts.js b/octofit-tracker/frontend/src/components/Workouts.js index 18c2a5e..eb9647c 100644 --- a/octofit-tracker/frontend/src/components/Workouts.js +++ b/octofit-tracker/frontend/src/components/Workouts.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { getApiUrl } from '../config/api'; const Workouts = () => { const [workouts, setWorkouts] = useState([]); @@ -7,7 +8,7 @@ const Workouts = () => { useEffect(() => { const fetchWorkouts = async () => { - const apiUrl = `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev/api/workouts/`; + const apiUrl = getApiUrl('/api/workouts/'); console.log('Fetching from Workouts API endpoint:', apiUrl); try { diff --git a/octofit-tracker/frontend/src/config/api.js b/octofit-tracker/frontend/src/config/api.js new file mode 100644 index 0000000..00e899c --- /dev/null +++ b/octofit-tracker/frontend/src/config/api.js @@ -0,0 +1,33 @@ +/** + * API Configuration + * Provides a centralized way to configure the API base URL + * with automatic fallback to localhost for local development + */ + +const getApiBaseUrl = () => { + // Check if we have a custom API base URL set + if (process.env.REACT_APP_API_BASE_URL) { + return process.env.REACT_APP_API_BASE_URL; + } + + // Check if we're in a GitHub Codespace + if (process.env.REACT_APP_CODESPACE_NAME) { + return `https://${process.env.REACT_APP_CODESPACE_NAME}-8000.app.github.dev`; + } + + // Default to localhost for local development + return 'http://localhost:8000'; +}; + +export const API_BASE_URL = getApiBaseUrl(); + +/** + * Helper function to build API endpoint URLs + * @param {string} endpoint - The API endpoint path (e.g., '/api/users/') + * @returns {string} - The full API URL + */ +export const getApiUrl = (endpoint) => { + // Ensure endpoint starts with / + const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + return `${API_BASE_URL}${normalizedEndpoint}`; +}; From 2fc6903580cad29bcc3f25ac08a4551ef22270e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:31:53 +0000 Subject: [PATCH 3/3] docs: Add API configuration documentation Co-authored-by: pmca31 <6774003+pmca31@users.noreply.github.com> --- octofit-tracker/frontend/API_CONFIG.md | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 octofit-tracker/frontend/API_CONFIG.md diff --git a/octofit-tracker/frontend/API_CONFIG.md b/octofit-tracker/frontend/API_CONFIG.md new file mode 100644 index 0000000..ccb0937 --- /dev/null +++ b/octofit-tracker/frontend/API_CONFIG.md @@ -0,0 +1,59 @@ +# API Configuration + +This application uses a centralized API configuration system that automatically adapts to different environments. + +## How It Works + +The API configuration (`src/config/api.js`) determines the backend API URL based on the following priority: + +1. **Custom API URL** (highest priority) + - Set via `REACT_APP_API_BASE_URL` environment variable + - Example: `REACT_APP_API_BASE_URL=http://localhost:8000` + - Use this to override the default behavior + +2. **GitHub Codespaces** + - Automatically detected via `REACT_APP_CODESPACE_NAME` environment variable + - Constructs URL: `https://${REACT_APP_CODESPACE_NAME}-8000.app.github.dev` + - No manual configuration needed in Codespaces + +3. **Local Development** (default fallback) + - Falls back to `http://localhost:8000` + - Works out of the box for local development + +## Configuration Examples + +### Local Development +No configuration needed! The app will automatically use `http://localhost:8000`. + +### GitHub Codespaces +The setup script automatically sets `REACT_APP_CODESPACE_NAME` in `.env`: +```bash +REACT_APP_CODESPACE_NAME=your-codespace-name +``` + +### Custom Backend URL +Create or update `.env` file: +```bash +REACT_APP_API_BASE_URL=http://192.168.1.100:8000 +# or +REACT_APP_API_BASE_URL=https://my-backend.example.com +``` + +## Usage in Components + +Import and use the `getApiUrl` helper function: + +```javascript +import { getApiUrl } from '../config/api'; + +// Use in your component +const apiUrl = getApiUrl('/api/users/'); +const response = await fetch(apiUrl); +``` + +## Benefits + +- **No more "undefined" URLs**: If environment variables are not set, the app falls back to localhost +- **Centralized configuration**: All API URLs are managed in one place +- **Easy to override**: Set `REACT_APP_API_BASE_URL` to point to any backend +- **Works everywhere**: Local development, Codespaces, production deployments