mirror of
https://github.com/farcasclaudiu/learn-build-apps-copilot-agent.git
synced 2026-06-22 07:01:37 +03:00
feat: add frontend components for activities, leaderboard, teams, users, and workouts; include environment configuration
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Vite environment variables for OctoFit Tracker
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# If unset, the frontend falls back to http://localhost:8000/api.
|
||||
|
||||
VITE_CODESPACE_NAME=your-codespace-name-here
|
||||
@@ -1,121 +1,41 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from './assets/vite.svg'
|
||||
import heroImg from './assets/hero.png'
|
||||
import './App.css'
|
||||
import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom'
|
||||
import Activities from './components/Activities'
|
||||
import Leaderboard from './components/Leaderboard'
|
||||
import Teams from './components/Teams'
|
||||
import Users from './components/Users'
|
||||
import Workouts from './components/Workouts'
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<section id="center">
|
||||
<div className="hero">
|
||||
<img src={heroImg} className="base" width="170" height="179" alt="" />
|
||||
<img src={reactLogo} className="framework" alt="React logo" />
|
||||
<img src={viteLogo} className="vite" alt="Vite logo" />
|
||||
<BrowserRouter>
|
||||
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div className="container-fluid">
|
||||
<span className="navbar-brand fw-bold">OctoFit Tracker</span>
|
||||
<div className="navbar-nav">
|
||||
<NavLink className={({ isActive }) => 'nav-link' + (isActive ? ' active' : '')} to="/activities">Activities</NavLink>
|
||||
<NavLink className={({ isActive }) => 'nav-link' + (isActive ? ' active' : '')} to="/leaderboard">Leaderboard</NavLink>
|
||||
<NavLink className={({ isActive }) => 'nav-link' + (isActive ? ' active' : '')} to="/teams">Teams</NavLink>
|
||||
<NavLink className={({ isActive }) => 'nav-link' + (isActive ? ' active' : '')} to="/users">Users</NavLink>
|
||||
<NavLink className={({ isActive }) => 'nav-link' + (isActive ? ' active' : '')} to="/workouts">Workouts</NavLink>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>
|
||||
Edit <code>src/App.jsx</code> and save to test <code>HMR</code>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="counter"
|
||||
onClick={() => setCount((count) => count + 1)}
|
||||
>
|
||||
Count is {count}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg className="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img className="logo" src={viteLogo} alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://react.dev/" target="_blank">
|
||||
<img className="button-icon" src={reactLogo} alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="container mt-4">
|
||||
<Routes>
|
||||
<Route path="/activities" element={<Activities />} />
|
||||
<Route path="/leaderboard" element={<Leaderboard />} />
|
||||
<Route path="/teams" element={<Teams />} />
|
||||
<Route path="/users" element={<Users />} />
|
||||
<Route path="/workouts" element={<Workouts />} />
|
||||
<Route path="/" element={
|
||||
<div className="text-center mt-5">
|
||||
<h1>Welcome to OctoFit Tracker</h1>
|
||||
<p className="lead">Use the navigation above to explore activities, leaderboard, teams, users, and workouts.</p>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg className="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
} />
|
||||
</Routes>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
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'
|
||||
|
||||
function Activities() {
|
||||
const [activities, setActivities] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/activities/`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setActivities(Array.isArray(data) ? data : (data.results ?? []))
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (loading) return <p>Loading activities…</p>
|
||||
if (error) return <p className="text-danger">Error: {error}</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Activities</h2>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Type</th>
|
||||
<th>Duration (min)</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{activities.map((activity) => (
|
||||
<tr key={activity._id ?? activity.id}>
|
||||
<td>{activity.user}</td>
|
||||
<td>{activity.activity_type}</td>
|
||||
<td>{activity.duration}</td>
|
||||
<td>{activity.date}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{activities.length === 0 && <p>No activities found.</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Activities
|
||||
@@ -0,0 +1,58 @@
|
||||
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'
|
||||
|
||||
function Leaderboard() {
|
||||
const [entries, setEntries] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/leaderboard/`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setEntries(Array.isArray(data) ? data : (data.results ?? []))
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (loading) return <p>Loading leaderboard…</p>
|
||||
if (error) return <p className="text-danger">Error: {error}</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Leaderboard</h2>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>User</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map((entry, index) => (
|
||||
<tr key={entry._id ?? entry.id}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{entry.user}</td>
|
||||
<td>{entry.score}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{entries.length === 0 && <p>No leaderboard entries found.</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Leaderboard
|
||||
@@ -0,0 +1,56 @@
|
||||
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'
|
||||
|
||||
function Teams() {
|
||||
const [teams, setTeams] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/teams/`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setTeams(Array.isArray(data) ? data : (data.results ?? []))
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (loading) return <p>Loading teams…</p>
|
||||
if (error) return <p className="text-danger">Error: {error}</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Teams</h2>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Members</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{teams.map((team) => (
|
||||
<tr key={team._id ?? team.id}>
|
||||
<td>{team.name}</td>
|
||||
<td>{Array.isArray(team.members) ? team.members.join(', ') : team.members}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{teams.length === 0 && <p>No teams found.</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Teams
|
||||
@@ -0,0 +1,56 @@
|
||||
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'
|
||||
|
||||
function Users() {
|
||||
const [users, setUsers] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/users/`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setUsers(Array.isArray(data) ? data : (data.results ?? []))
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (loading) return <p>Loading users…</p>
|
||||
if (error) return <p className="text-danger">Error: {error}</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Users</h2>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user._id ?? user.id}>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.email}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{users.length === 0 && <p>No users found.</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Users
|
||||
@@ -0,0 +1,56 @@
|
||||
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'
|
||||
|
||||
function Workouts() {
|
||||
const [workouts, setWorkouts] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/workouts/`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
setWorkouts(Array.isArray(data) ? data : (data.results ?? []))
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (loading) return <p>Loading workouts…</p>
|
||||
if (error) return <p className="text-danger">Error: {error}</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Workouts</h2>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{workouts.map((workout) => (
|
||||
<tr key={workout._id ?? workout.id}>
|
||||
<td>{workout.name}</td>
|
||||
<td>{workout.description}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{workouts.length === 0 && <p>No workouts found.</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Workouts
|
||||
@@ -1,5 +1,6 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user