Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 54 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PwrProgram

A comprehensive fitness program management API built with TypeScript, Node.js, and PostgreSQL. Manage workout programs, cycles, blocks, sessions, exercises, and sets with a hierarchical structure designed for coaches and athletes.
A comprehensive fitness program management application built with TypeScript, React, Node.js, and PostgreSQL. Manage workout programs, cycles, blocks, sessions, exercises, and sets with a hierarchical structure designed for coaches and athletes.

## 🚀 Features

Expand All @@ -11,6 +11,14 @@ A comprehensive fitness program management API built with TypeScript, Node.js, a
- **Pagination**: Efficient data retrieval with configurable page sizes
- **HATEOAS**: Discoverable REST API with hypermedia links

### Frontend (React + Vite)
- **Modern Stack**: React 19, TypeScript, Vite
- **Routing**: React Router v7 with protected routes
- **Authentication**: Context-based auth with session management
- **Component Library**: Reusable UI components (Button, Input, Card)
- **CSS Modules**: Scoped styling for maintainability
- **API Integration**: Type-safe API client with error handling

### Security
- ✅ **Password Hashing**: Bcrypt with configurable rounds
- ✅ **Session Management**: Database-backed sessions (TypeORM store, scalable to Redis)
Expand Down Expand Up @@ -142,26 +150,32 @@ All configuration is done through environment variables. Copy `.env.example` to

### Development
```bash
# Start dev server with hot reload
pnpm --filter @pwrprogram/api dev
# Start API server with hot reload
pnpm dev:api

# Run tests
pnpm --filter @pwrprogram/api test
# Start frontend dev server
pnpm dev:web

# Run API tests
pnpm test:api

# Run tests with coverage
pnpm --filter @pwrprogram/api test:coverage
pnpm test:api:coverage

# Lint code
pnpm --filter @pwrprogram/api lint
# Lint frontend code
pnpm lint:web
```

### Production
```bash
# Build the application
# Build the API
pnpm --filter @pwrprogram/api build

# Start production server
pnpm --filter @pwrprogram/api start
# Build the frontend
pnpm build:web

# Start production API server
pnpm start:api
```

### Docker
Expand Down Expand Up @@ -430,18 +444,34 @@ docker run -d \
```
PwrProgram/
├── apps/
│ └── pwrprogram/ # Main API application
│ ├── pwrprogram/ # Main API application
│ │ ├── src/
│ │ │ ├── entity/ # TypeORM entities
│ │ │ ├── routes/ # Express route handlers
│ │ │ ├── middleware/ # Custom middleware
│ │ │ ├── mappers/ # Entity to DTO mappers
│ │ │ ├── utils/ # Utility functions
│ │ │ ├── openapi/ # OpenAPI spec
│ │ │ ├── testing/ # Test files
│ │ │ ├── data-source.ts # TypeORM configuration
│ │ │ └── index.ts # Application entry point
│ │ ├── .env # Environment variables (gitignored)
│ │ └── package.json
│ └── web/ # React frontend application
│ ├── src/
│ │ ├── entity/ # TypeORM entities
│ │ ├── routes/ # Express route handlers
│ │ ├── middleware/ # Custom middleware
│ │ ├── mappers/ # Entity to DTO mappers
│ │ ├── utils/ # Utility functions
│ │ ├── openapi/ # OpenAPI spec
│ │ ├── testing/ # Test files
│ │ ├── data-source.ts # TypeORM configuration
│ │ └── index.ts # Application entry point
│ ├── .env # Environment variables (gitignored)
│ │ ├── components/ # React components
│ │ │ ├── auth/ # Auth-related components
│ │ │ ├── layout/ # Layout components
│ │ │ ├── programs/ # Program management
│ │ │ └── ui/ # Reusable UI components
│ │ ├── context/ # React contexts (Auth)
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utilities and API client
│ │ ├── pages/ # Page components
│ │ ├── types/ # TypeScript types
│ │ ├── App.tsx # App with routing
│ │ └── main.tsx # Entry point
│ ├── vite.config.ts # Vite configuration
│ └── package.json
├── packages/
│ └── shared/ # Shared DTOs and types
Expand Down Expand Up @@ -546,7 +576,5 @@ PwrProgram/
- [Express.js](https://expressjs.com/)
- [PostgreSQL](https://www.postgresql.org/)
- [TypeScript](https://www.typescriptlang.org/)

---

**Note**: This is a backend API. Frontend coming soon!
- [React](https://react.dev/)
- [Vite](https://vitejs.dev/)
24 changes: 24 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
73 changes: 73 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...

// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,

// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
29 changes: 29 additions & 0 deletions apps/web/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowExportNames: ['AuthContext', 'AuthContextType'] },
],
},
},
])
13 changes: 13 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PwrProgram - Fitness Program Management</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@pwrprogram/web",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@pwrprogram/shared": "workspace:*",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.6.1"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}
1 change: 1 addition & 0 deletions apps/web/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions apps/web/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* Global App Styles */
* {
box-sizing: border-box;
}

#root {
min-height: 100vh;
}

a {
text-decoration: none;
color: inherit;
}

/* Links styled by components */
a:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}

58 changes: 58 additions & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from '@/context';
import { MainLayout } from '@/components/layout';
import { ProtectedRoute } from '@/components/auth';
import {
HomePage,
LoginPage,
RegisterPage,
DashboardPage,
ProgramsPage,
ProgramDetailPage,
} from '@/pages';
import './App.css';

function App() {
return (
<AuthProvider>
<BrowserRouter>
<Routes>
<Route element={<MainLayout />}>
{/* Public routes */}
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />

{/* Protected routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
<Route
path="/programs"
element={
<ProtectedRoute>
<ProgramsPage />
</ProtectedRoute>
}
/>
<Route
path="/programs/:id"
element={
<ProtectedRoute>
<ProgramDetailPage />
</ProtectedRoute>
}
/>
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
);
}

export default App;
1 change: 1 addition & 0 deletions apps/web/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading