diff --git a/octofit-tracker/frontend/.env.local.example b/octofit-tracker/frontend/.env.local.example index 33d9170..1b68758 100644 --- a/octofit-tracker/frontend/.env.local.example +++ b/octofit-tracker/frontend/.env.local.example @@ -3,10 +3,10 @@ # Copy this file to .env.local and fill in your values. # .env.local is gitignored and never committed. # -# VITE_CODESPACE_NAME is required when running inside a GitHub Codespace. -# It is used to build the backend API base URL: -# https://${VITE_CODESPACE_NAME}-8000.app.github.dev/api +# VITE_CODESPACE_NAME can be used to explicitly point at the backend API. +# When running inside a GitHub Codespace, the frontend will also auto-detect +# the backend host from the current app.github.dev URL. # -# If unset, the frontend falls back to http://localhost:8000/api. +# If unset outside Codespaces, the frontend falls back to http://localhost:8000/api. VITE_CODESPACE_NAME=your-codespace-name-here diff --git a/octofit-tracker/frontend/src/components/Activities.jsx b/octofit-tracker/frontend/src/components/Activities.jsx index 64fdb11..fcb390e 100644 --- a/octofit-tracker/frontend/src/components/Activities.jsx +++ b/octofit-tracker/frontend/src/components/Activities.jsx @@ -1,9 +1,5 @@ import { useEffect, useState } from 'react' - -const codespaceName = import.meta.env.VITE_CODESPACE_NAME -const API_BASE = codespaceName - ? `https://${codespaceName}-8000.app.github.dev/api` - : 'http://localhost:8000/api' +import { API_BASE } from '../lib/apiBase' function Activities() { const [activities, setActivities] = useState([]) @@ -17,7 +13,7 @@ function Activities() { return res.json() }) .then((data) => { - setActivities(Array.isArray(data) ? data : (data.results ?? [])) + setActivities(Array.isArray(data) ? data : (data.items ?? data.results ?? [])) setLoading(false) }) .catch((err) => { @@ -36,18 +32,22 @@ function Activities() { User + Team Type Duration (min) - Date + Calories + Completed {activities.map((activity) => ( - {activity.user} - {activity.activity_type} - {activity.duration} - {activity.date} + {activity.user?.username ?? activity.user} + {activity.team?.name ?? activity.team} + {activity.type} + {activity.durationMinutes} + {activity.caloriesBurned} + {activity.completedAt ? new Date(activity.completedAt).toLocaleString() : ''} ))} diff --git a/octofit-tracker/frontend/src/components/Leaderboard.jsx b/octofit-tracker/frontend/src/components/Leaderboard.jsx index 431b91f..342378f 100644 --- a/octofit-tracker/frontend/src/components/Leaderboard.jsx +++ b/octofit-tracker/frontend/src/components/Leaderboard.jsx @@ -1,12 +1,8 @@ import { useEffect, useState } from 'react' - -const codespaceName = import.meta.env.VITE_CODESPACE_NAME -const API_BASE = codespaceName - ? `https://${codespaceName}-8000.app.github.dev/api` - : 'http://localhost:8000/api' +import { API_BASE } from '../lib/apiBase' function Leaderboard() { - const [entries, setEntries] = useState([]) + const [leaderboards, setLeaderboards] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -17,7 +13,7 @@ function Leaderboard() { return res.json() }) .then((data) => { - setEntries(Array.isArray(data) ? data : (data.results ?? [])) + setLeaderboards(Array.isArray(data) ? data : (data.items ?? data.results ?? [])) setLoading(false) }) .catch((err) => { @@ -35,22 +31,26 @@ function Leaderboard() { + - {entries.map((entry, index) => ( - - - - - - ))} + {leaderboards.flatMap((leaderboard) => + leaderboard.entries.map((entry) => ( + + + + + + + )), + )}
Period Rank User Score
{index + 1}{entry.user}{entry.score}
{leaderboard.period}{entry.rank}{entry.user?.username ?? entry.user}{entry.points}
- {entries.length === 0 &&

No leaderboard entries found.

} + {leaderboards.length === 0 &&

No leaderboard entries found.

} ) } diff --git a/octofit-tracker/frontend/src/components/Teams.jsx b/octofit-tracker/frontend/src/components/Teams.jsx index 6f27d63..35de16f 100644 --- a/octofit-tracker/frontend/src/components/Teams.jsx +++ b/octofit-tracker/frontend/src/components/Teams.jsx @@ -1,9 +1,5 @@ import { useEffect, useState } from 'react' - -const codespaceName = import.meta.env.VITE_CODESPACE_NAME -const API_BASE = codespaceName - ? `https://${codespaceName}-8000.app.github.dev/api` - : 'http://localhost:8000/api' +import { API_BASE } from '../lib/apiBase' function Teams() { const [teams, setTeams] = useState([]) @@ -17,7 +13,7 @@ function Teams() { return res.json() }) .then((data) => { - setTeams(Array.isArray(data) ? data : (data.results ?? [])) + setTeams(Array.isArray(data) ? data : (data.items ?? data.results ?? [])) setLoading(false) }) .catch((err) => { @@ -36,6 +32,7 @@ function Teams() { Name + Captain Members @@ -43,7 +40,12 @@ function Teams() { {teams.map((team) => ( {team.name} - {Array.isArray(team.members) ? team.members.join(', ') : team.members} + {team.captain?.username ?? team.captain} + + {Array.isArray(team.members) + ? team.members.map((member) => member?.username ?? member).join(', ') + : team.members} + ))} diff --git a/octofit-tracker/frontend/src/components/Users.jsx b/octofit-tracker/frontend/src/components/Users.jsx index d429790..c3e7a41 100644 --- a/octofit-tracker/frontend/src/components/Users.jsx +++ b/octofit-tracker/frontend/src/components/Users.jsx @@ -1,9 +1,5 @@ import { useEffect, useState } from 'react' - -const codespaceName = import.meta.env.VITE_CODESPACE_NAME -const API_BASE = codespaceName - ? `https://${codespaceName}-8000.app.github.dev/api` - : 'http://localhost:8000/api' +import { API_BASE } from '../lib/apiBase' function Users() { const [users, setUsers] = useState([]) @@ -17,7 +13,7 @@ function Users() { return res.json() }) .then((data) => { - setUsers(Array.isArray(data) ? data : (data.results ?? [])) + setUsers(Array.isArray(data) ? data : (data.items ?? data.results ?? [])) setLoading(false) }) .catch((err) => { diff --git a/octofit-tracker/frontend/src/components/Workouts.jsx b/octofit-tracker/frontend/src/components/Workouts.jsx index 8aacc78..cfb4c1a 100644 --- a/octofit-tracker/frontend/src/components/Workouts.jsx +++ b/octofit-tracker/frontend/src/components/Workouts.jsx @@ -1,9 +1,5 @@ import { useEffect, useState } from 'react' - -const codespaceName = import.meta.env.VITE_CODESPACE_NAME -const API_BASE = codespaceName - ? `https://${codespaceName}-8000.app.github.dev/api` - : 'http://localhost:8000/api' +import { API_BASE } from '../lib/apiBase' function Workouts() { const [workouts, setWorkouts] = useState([]) @@ -17,7 +13,7 @@ function Workouts() { return res.json() }) .then((data) => { - setWorkouts(Array.isArray(data) ? data : (data.results ?? [])) + setWorkouts(Array.isArray(data) ? data : (data.items ?? data.results ?? [])) setLoading(false) }) .catch((err) => { @@ -35,15 +31,19 @@ function Workouts() { - - + + + + {workouts.map((workout) => ( - - + + + + ))} diff --git a/octofit-tracker/frontend/src/lib/apiBase.js b/octofit-tracker/frontend/src/lib/apiBase.js new file mode 100644 index 0000000..f2afe58 --- /dev/null +++ b/octofit-tracker/frontend/src/lib/apiBase.js @@ -0,0 +1,15 @@ +const codespaceName = import.meta.env.VITE_CODESPACE_NAME +const browserHost = typeof window !== 'undefined' ? window.location.hostname : '' +const isDevelopment = import.meta.env.DEV + +const derivedCodespaceHost = browserHost.endsWith('.app.github.dev') + ? browserHost.replace(/-\d+\.app\.github\.dev$/, '-8000.app.github.dev') + : '' + +export const API_BASE = isDevelopment + ? '/api' + : codespaceName + ? `https://${codespaceName}-8000.app.github.dev/api` + : derivedCodespaceHost + ? `https://${derivedCodespaceHost}/api` + : 'http://localhost:8000/api' \ No newline at end of file diff --git a/octofit-tracker/frontend/vite.config.js b/octofit-tracker/frontend/vite.config.js index d3668a3..da3c1f6 100644 --- a/octofit-tracker/frontend/vite.config.js +++ b/octofit-tracker/frontend/vite.config.js @@ -5,6 +5,12 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { + proxy: { + '/api': { + target: 'http://127.0.0.1:8000', + changeOrigin: true, + }, + }, port: 5173, strictPort: true, },
NameDescriptionTitleCategoryDifficultyDuration (min)
{workout.name}{workout.description}{workout.title}{workout.category}{workout.difficulty}{workout.durationMinutes}