A client management and scheduling system for personal trainers built with Next.js 15, PostgreSQL, and TypeScript.
- Framework: Next.js 15.1.3 with App Router and React 19
- Language: TypeScript 5.7
- Database: PostgreSQL with Prisma ORM 6.3.1
- Authentication: Auth.js v5 (NextAuth) with JWT strategy and credentials provider
- UI Components: shadcn/ui components built on Radix UI primitives
- Styling: Tailwind CSS 3.2 with custom configuration
- State Management: TanStack Query (React Query) v5 for server state
- Forms: React Hook Form with Zod validation
- CMS Integration: Contentful for dynamic content
- Email: Resend for transactional emails (invoices, password resets, form submissions)
- Calendar Integration: Google Calendar API for appointment synchronization
- Error Monitoring: Sentry
- Testing: Vitest for unit tests, Playwright for E2E tests
- Deployment: Vercel with GitHub Actions CI/CD
├── app/ # Next.js App Router pages and API routes
│ ├── (users)/ # Protected routes for authenticated users
│ │ ├── appointment/ # Individual appointment views
│ │ ├── bootcamp/ # Group class views
│ │ ├── calendar/ # Main calendar interface
│ │ └── forms/ # Dynamic forms from Contentful
│ ├── admin/ # Admin-only routes (role-based)
│ │ ├── bootcamps/ # Bootcamp management
│ │ ├── calendar/ # Admin calendar view
│ │ └── users/ # User management interface
│ ├── api/ # API routes
│ │ └── auth/ # Authentication endpoints
│ ├── actions/ # Server actions for data mutations
│ └── hooks/ # Custom React hooks for data fetching
├── features/ # Feature-based components
│ ├── bootcamps/ # Bootcamp-specific components
│ ├── calendar/ # Calendar components
│ │ ├── appointment/ # Appointment event components
│ │ ├── bootcamp/ # Bootcamp event components
│ │ ├── desktop/ # Desktop calendar layout
│ │ ├── mobile/ # Mobile calendar layout
│ │ └── workout/ # Workout event components
│ └── users/ # User management components
├── components/ # Shared UI components (shadcn/ui)
├── lib/ # Core utilities and business logic
├── prisma/ # Database schema and migrations
├── server/ # Standalone server (if needed)
├── e2e/ # End-to-end tests
└── public/ # Static assets
The application manages three primary event types and two user types:
-
Appointments (
EVENT_TYPE.APPOINTMENT)- One-on-one training sessions
- Fee-based with billing tracking
- Attendance status tracking (ATTENDED/NOT_ATTENDED)
- Associated with individual users
- Automatic synchronization with Google Calendar
-
Bootcamps (
EVENT_TYPE.BOOTCAMP)- Group training sessions
- Credit-based attendance system
- Many-to-many relationship with users
- No individual fees
-
Workouts (
EVENT_TYPE.WORKOUT)- Assigned exercise programs
- Status tracking (NOT_STARTED/COMPLETED)
- Individual user assignments
- INDIVIDUAL: Standard clients with per-appointment fees
- BOOTCAMP: Group class participants using a credit system
The PostgreSQL database uses Prisma ORM with the following main models:
User: Core user entity with authentication, billing, and type informationAppointment: Individual training sessions with fees and attendanceBootcamp: Group sessions with attendee relationshipsWorkout: Exercise assignments with completion statusInvoice: Billing records aggregating appointmentsAccount,Session,VerificationToken: Auth.js authentication models
- Credentials Provider: Email/password authentication with bcrypt hashing
- JWT Strategy: Stateless session management with 30-day expiry
- Role-Based Access: Two roles -
user(clients) andadmin(trainers) - Password Reset: Token-based reset flow via email
/admin/*: Requires admin role/(users)/*: Requires authentication- Public routes:
/auth/*, root page
- Node.js 18+
- PostgreSQL 14+
- npm or yarn
- Copy the environment template:
cp .env.example .env- Required environment variables:
Database:
DATABASE_URL: PostgreSQL connection string
Authentication:
NEXTAUTH_SECRET: Random string for JWT signingNEXTAUTH_URL: Application URL (http://localhost:3000 for development)
Email (Resend):
RESEND_API_KEY: API key from resend.comEMAIL_FROM: Sender email address (must use verified Resend domain, e.g., noreply@send.yourdomain.com)EMAIL_TO: Admin email for receiving form submissions
Contentful CMS:
CONTENTFUL_SPACE_ID: Space identifierCONTENTFUL_ACCESS_TOKEN: Content Delivery API tokenCONTENTFUL_MANAGEMENT_API_ACCESS_TOKEN: Management API token
Google Calendar (Optional):
GOOGLE_CALENDAR_ID: Calendar ID for appointment syncGOOGLE_SERVICE_ACCOUNT_EMAIL: Service account emailGOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private keyTIME_ZONE: Timezone for calendar events (default: Europe/London)
Branding:
PT_BRAND_NAME: Business nameNEXT_PUBLIC_PT_FIRST_NAME: Trainer's first name for UI
# Install dependencies
npm install
# Generate Prisma client
npx prisma generate
# Run database migrations
npx prisma migrate deploy
# Seed database (optional)
npx prisma db seed
# Start development server
npm run devThe application runs at http://localhost:3000
For local development with Docker:
docker run --name ptp-db \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=ptp \
-p 5432:5432 \
-d postgresnpm run dev # Start development server with Turbopack
npm run build # Build for production
npm run start # Start production server
npm run lint # Run ESLint with auto-fix
npm run format # Format code with Prettier
npm run test:unit # Run unit tests (Vitest)
npm run test:e2e # Run E2E tests (Playwright)
npm test # Run all tests
npx prisma migrate dev # Create new migration
npx prisma studio # Open Prisma Studio GUI- Server Components: Direct database queries via Prisma in RSC
- Client Components: TanStack Query for mutations and optimistic updates
- Server Actions: Form submissions and data mutations
- Prefetching: Adjacent month calendar data prefetching
- Server State: TanStack Query with stale-while-revalidate
- Form State: React Hook Form with Zod schemas
- URL State: Query parameters for calendar navigation
The calendar features responsive design with separate implementations:
- Desktop: Full month grid view with drag interactions
- Mobile: Compact week view with touch optimization
- Event Types: Color-coded by type with distinct UI patterns
- Appointment Fees: Tracked per appointment with attendance status
- Invoice Generation: Monthly aggregation of attended appointments
- Credit System: Pre-purchased credits for bootcamp attendance
- Email Invoices: Automated monthly billing emails
- Revenue Dashboard: Real-time total revenue calculation in user summary
- CSV Export: Monthly revenue export for tax reporting with UK tax year support
The application supports automatic synchronization of appointments with Google Calendar using a service account. This allows trainers to manage their schedule in one place while keeping their Google Calendar updated.
- Automatic Sync: Appointments are automatically added to Google Calendar when created
- Bidirectional Updates: Changes to appointments update the corresponding Google Calendar events
- Soft Delete Handling: Deleted appointments are removed from Google Calendar
- Resilient Sync: Database operations succeed even if Google Calendar sync fails
- User Feedback: Toast notifications inform users of sync status
- Recurring Appointments: Correctly syncs multiple appointments with their individual dates and times
- Create a Google Cloud project and enable the Calendar API
- Create a service account and download the JSON key file
- Share your Google Calendar with the service account email (with write permissions)
- Add the following environment variables:
GOOGLE_CALENDAR_ID: Your calendar ID (found in Calendar settings)GOOGLE_SERVICE_ACCOUNT_EMAIL: Service account emailGOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: Private key from JSON fileTIME_ZONE: Your timezone (e.g., 'Europe/London')
- Appointments store the Google Calendar event ID for tracking
- Batch operations for creating multiple recurring appointments
- Graceful error handling with user notifications
- Manual retry capability for failed syncs (via
syncAppointmentToGoogleCalendar)
The application includes comprehensive revenue tracking and reporting features designed for tax compliance and financial oversight.
Located at /admin/users, the summary view provides:
- Real-time Revenue Totals: Automatic calculation of total revenue from filtered users
- Filterable Data: Revenue updates dynamically based on table filters (date range, user type, etc.)
- Per-User Metrics: Individual revenue, appointments booked/attended, and invoice status
The CSV export feature allows trainers to export monthly revenue totals for tax purposes:
- UK Tax Year Support: Default date range set to current UK tax year (April 6 - April 5)
- Monthly Aggregation: Revenue grouped by month for simplified tax reporting
- Custom Date Ranges: Select any start and end date for reporting periods
- Formatted Output: Clean CSV format with headers, monthly totals, and grand total
Example CSV output:
Revenue Summary: 6 Apr 2024 - 5 Apr 2025
Month,Revenue (£)
April 2024,1250.00
May 2024,1400.00
June 2024,1600.00
...
Total,15650.00Revenue calculations follow functional design principles:
- Pure Functions (
lib/csv-export.ts): Date calculations and CSV formatting - Server Actions (
app/actions/csv-export.ts): Admin-authorized data fetching - UI Components (
features/users/summary/ExportCSVDialog.tsx): User interaction and file download
- Business logic in
/lib - Component testing with React Testing Library
- Mock external dependencies via MSW
- Critical user flows
- Multi-browser testing (Chrome, Safari)
- Mobile viewport testing
- CI integration with GitHub Actions
- Hosting: Vercel with automatic deployments
- Database: PostgreSQL on Railway or similar
- Environment: Separate staging and production configs
- Monitoring: Sentry for error tracking
GitHub Actions workflow:
- Runs linting and formatting checks
- Executes unit and E2E test suites
- Builds production bundle
- Deploys to Vercel on merge to main
Automated backup script available:
npm run backup- Turbopack: Fast HMR in development
- Server Components: Reduced client bundle size
- Image Optimization: Next.js Image component with remote patterns
- Code Splitting: Automatic route-based splitting
- Prefetching: Strategic data prefetching for calendar navigation
- Authentication: Secure JWT implementation with httpOnly cookies
- Password Storage: bcrypt hashing with salt rounds
- Input Validation: Zod schemas on all user inputs
- SQL Injection: Protected via Prisma parameterized queries
- CORS: Configured for production domains
- Environment Variables: Sensitive data never committed to repository
- Branch from
main - Follow existing code patterns and conventions
- Write tests for new features
- Ensure all tests pass before submitting PR
- Update documentation as needed
MIT