docs: rewrite all documentation for v2 stack (MongoDB, Express 5, /manage dashboard)

This commit is contained in:
friday-bot 2026-05-27 17:21:09 -06:00
parent 5fc019462c
commit 4fa0ff23d4
6 changed files with 359 additions and 472 deletions

176
README.md
View file

@ -1,81 +1,145 @@
# Missoula Council of the Deaf, Inc. Website
# Missoula Council of the Deaf, Inc. (MCDi) Website
This repository contains the source code for the Missoula Council of the Deaf, Inc. (MCDi) website. The project is built using a modern web stack with React for the frontend and Express.js for the backend, both implemented in TypeScript. The application is containerized using Docker for easy deployment and scalability.
Official website for MCDi at [deafmissoula.org](https://deafmissoula.org). Built on a full-stack TypeScript architecture with a MongoDB backend and a React frontend.
## Features
## Tech Stack
- Responsive design using Tailwind CSS
- Dynamic UI components with React and Framer Motion for animations
- TypeScript for enhanced type safety and developer experience
- Express.js backend serving static React files and handling API requests
- Docker containerization for consistent development and deployment environments
- Calendar functionality with upcoming events display
- About Us section with board member information
- Bylaws and Minutes sections for organizational transparency
- Sponsor showcase
- Contact form in the footer
| Layer | Technology |
|-------|-----------|
| Frontend | React 19, TypeScript 5.8, Tailwind CSS, Framer Motion |
| Backend | Node.js 22 LTS, Express 5.1, TypeScript 5.8 |
| Database | MongoDB 7.0 (Mongoose 8.x ODM) |
| Auth | JWT (`jose` 5.x), `argon2` password hashing |
| File uploads | `multer` 1.4.5-lts |
| Email | `nodemailer` (Google Workspace SMTP) |
| Containerization | Docker, Docker Compose |
| Reverse proxy | Caddy (on production server) |
## Key Components
## Repository Structure
### Client-side
```
deafmissoula-website/
├── client/ # React frontend (Create React App)
│ ├── public/ # Static assets (photos, videos, PDFs, headshots)
│ └── src/
│ └── components/ # React components
├── server/ # Express backend
│ └── src/
│ ├── models/ # Mongoose models
│ ├── routes/ # API route handlers
│ ├── middleware/ # Auth, upload, error handling
│ ├── index.ts # Server entry point
│ ├── db.ts # MongoDB connection
│ └── seed.ts # Initial data seed script
├── docker-compose.yml # Production compose (2 containers: app + mongodb)
├── docker-compose.dev.yml # Dev override (no caddy_network requirement)
├── Dockerfile # Multi-stage build: client build + server compile
└── stack.env # Environment variables (committed — Gitea is internal-only)
```
- `App.tsx`: Main application component with routing setup
- `Header.tsx`: Navigation component
- `Home.tsx`: Landing page with featured content
- `Calendar.tsx`: Interactive calendar with event display
- `Meet-Board.tsx`: Board member profiles
- `Bylaws.tsx`: Organization bylaws display
- `Minutes.tsx`: Meeting minutes display
- `Sponsors.tsx`: Sponsor information and logos
- `Footer.tsx`: Footer component with contact form
## API Routes
### Server-side
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/auth/login` | — | Email + password login (rate limited: 5/5 min) |
| POST | `/api/auth/forgot-password` | — | Send password reset email |
| POST | `/api/auth/reset-password` | — | Set new password via reset token |
| POST | `/api/auth/change-password` | temp JWT | Forced password change on first login |
| GET | `/api/auth/me` | JWT | Current user info |
| GET/POST/PUT/DELETE | `/api/board` | JWT (write) | Board members |
| PATCH | `/api/board/reorder` | JWT | Drag-and-drop reorder |
| GET/POST/PUT/DELETE | `/api/events` | JWT (write) | Calendar events |
| GET/POST/PUT/DELETE | `/api/gallery` | JWT (write) | Photo gallery items |
| GET/POST/PUT/DELETE | `/api/minutes` | JWT (write) | Meeting minutes |
| PATCH | `/api/minutes/reorder` | JWT | Drag-and-drop reorder |
| GET/POST/PUT/DELETE | `/api/sponsors` | JWT (write) | Sponsors |
| GET/POST/PUT/DELETE | `/api/members` | JWT | Member directory |
| POST | `/api/email` | — | Contact form submission |
- `index.ts`: Express.js server setup
- `sendEmail.ts`: Email sending functionality for contact form
## Dashboard
## Getting Started
Available at `/manage` (intentionally not `/admin`). Board members log in with their `@deafmissoula.org` email address.
To run this project locally:
**First login flow:** Board members are created with `mustChangePassword: true`. On first login they receive a temp JWT and are immediately redirected to set their own password.
1. Clone the repository
2. Install Docker and Docker Compose
3. Create a `.env` file in the root directory with necessary environment variables
4. Run `docker-compose up --build` in the project root directory
5. Access the website at `http://localhost:5000`
**Forgot password:** Sends a 1-hour reset link to the member's email via Google Workspace SMTP.
## Development
**New board member:** When added via the dashboard, a welcome email is automatically sent with a 7-day set-password link.
- Client-side development: `cd client && npm start`
- Server-side development: `cd server && npm run dev`
- Build client: `cd client && npm run build`
- Build server: `cd server && npm run build`
## Environment Variables (`stack.env`)
## Deployment
```env
CACHEBUST=... # Increment to bust Docker layer cache
MONGO_INITDB_ROOT_USERNAME=mcdi
MONGO_INITDB_ROOT_PASSWORD=...
MONGO_INITDB_DATABASE=mcdi
MONGODB_URI=mongodb://mcdi:...@mongodb:27017/mcdi?authSource=admin
JWT_SECRET=...
ADMIN_PASSWORD=... # Initial password for admin@deafmissoula.org
APP_URL=https://deafmissoula.org # Used in password reset email links
```
The project is set up for easy deployment using Docker. The `Dockerfile` includes a multi-stage build process for both client and server, optimizing the final image size.
Google email credentials live in `.env` (not committed):
```env
GOOGLE_EMAIL=system@deafgain.org
GOOGLE_APP_PASSWORD=...
```
## Deployment with Docker Compose
## Local Development (Preview)
This project is containerized using Docker for easy deployment. Follow these steps to deploy the MCDi website:
```bash
# Start MongoDB separately (or use docker compose)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
1. Ensure Docker and Docker Compose are installed on your system.
# Seed the database (first run only)
cd server && npm run seed
2. Navigate to the project root directory containing the docker-compose.yml file.
# Dev servers
cd client && npm start # React dev server on :3000
cd server && npm run dev # Express with tsx watch on :801
```
3. Create a .env file in the root directory with the necessary environment variables:
NODE_ENV=production
GOOGLE_EMAIL=your-email@gmail.com GOOGLE_APP_PASSWORD=your-app-password
## Production Deployment
4. Build and start the containers:
docker-compose up --build
Production server: `10.4.0.205` (WireGuard VPN required)
Site path: `/home/chaulmark/websites/deafmissoula.org/`
5. Access the application at http://localhost:4000
```bash
ssh chaulmark@10.4.0.205
cd ~/websites/deafmissoula.org
git pull origin main
docker compose down
docker compose up -d --build
docker exec deafmissoulaorg-app-1 node dist/seed.js # First deploy only
```
6. To stop the containers:
docker-compose down
**Note on Caddy:** The app container joins the external `caddy_network`, so Caddy can route to it by container name:
```
deafmissoula.org {
reverse_proxy deafmissoulaorg-app-1:801
}
```
If you see a 502 after a rebuild, check that the container name hasn't changed (`docker ps`).
7. Use Docker Compose in detached mode:
docker-compose up -d
## Rollback to v1
This setup allows for easy deployment and management of the MCDi website container, with the ability to scale and update as needed using Docker Compose.
The last hardcoded (no-database) version is tagged `v1` in Git:
```bash
ssh chaulmark@10.4.0.205
cd ~/websites/deafmissoula.org
git checkout v1
docker compose down
docker compose up -d --build
```
v1 had no MongoDB container — `docker compose down` will cleanly remove the v2 stack. The MongoDB data volume (`mcdi-mongo-data`) is preserved unless you explicitly run `docker compose down -v`.
## Docker Volumes
| Volume | Purpose |
|--------|---------|
| `mcdi-mongo-data` | MongoDB data (persists across rebuilds) |
| `mcdi-uploads-data` | User-uploaded files via dashboard (`/uploads/`) |
Static assets baked into the image (photos, videos, PDFs from `client/public/`) are served directly from `client/build/` at their original paths (e.g. `/photos/0001.jpg`, `/minutes/minutes-04122025.pdf`). New uploads from the dashboard go to `/uploads/headshots/`, `/uploads/photos/`, etc.

View file

@ -1,177 +1,52 @@
# Active Context
## Current Status (March 10, 2026)
**Website Fully Operational** - All features deployed and verified working correctly
- Events calendar current and accurate
- All galleries (Photo, Video, Sponsors) functioning
- Minutes component working with latest documents
- Mobile donation button displaying properly
- All React 19 upgrades stable in production
- NEW: Resources page with Montana & National Deaf resources directory
## Current Status (May 27, 2026)
## Recent Changes
1. **Mobile Menu Fix (March 10, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED
- Fixed mobile hamburger menu to include all items from desktop "About MCDi" dropdown
- Added missing items: Meet MCDi Board, Bylaws, Minutes, Sponsors
- Mobile menu now shows all 11 navigation items (complete parity with desktop)
- Committed to Git: bfa0756
- Pushed to remote repository
- Deployed to production server (10.4.0.205)
- Docker container rebuilt with --no-cache flag
- Verified working on public website (HTTP 200)
**v2 Fully Deployed** — MongoDB backend live on production
2. **Resources Page (March 10, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED
- Created comprehensive Resources directory with Montana and National Deaf resources
- Organized into 9 categories:
* National Advocacy
* Montana Advocacy & Legal Support
* Family & Community Support
* Language Access & ASL Resources
* Education
* Employment Services
* Interpreter & Communication Access
* Montana Deaf Community Organizations
* Deaf Youth Camps, Sports & Leadership
- Professional card-based design with contact information (phone, email, address, website)
- Responsive 2-column layout on desktop, single column on mobile
- Smooth animations with Framer Motion
- Added to navigation: "About MCDi" dropdown and mobile menu
- Route: /resources
- Committed to Git: added54
- Pushed to remote repository
- Deployed to production server (10.4.0.205)
- Docker container rebuilt successfully
- Verified working on public website (HTTP 200)
The site completed a full backend migration from a hardcoded static Express app (v1) to a MongoDB-backed API with a board management dashboard (v2). Frontend is visually identical to v1 — no public-facing changes.
2. **Events Calendar Update (March 4, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED
- Removed 2 past events: January 11, 2025 Members Meeting and December 13, 2025 Board Meeting
- Updated March 14, 2026 Board Meeting with complete details:
* Location: Funk It Coffee and Thrift
* Address: 314 N 1st St. West, Missoula MT 59802
* Time: 10:00 AM - 11:00 AM (changed from 9:00 AM - 10:00 AM)
* Contact: Tessa Williams
- Kept all future events (June 2026 and September 2026 meetings)
- Committed to Git: 3eff1a9
- Pushed to remote repository
- Deployed to production server (10.4.0.205)
- Docker container rebuilt with --no-cache flag
- Verified working on public website (after browser cache clear)
- Changes are live and visible to public
## What Changed in v2
2. **Web Package Updates (Dec 15, 2025)**: ✅ FULLY COMPLETED
- Updated React 18.2.0 → 19.2.3 (major version upgrade)
- Updated React Router 6.17.0 → 7.10.1 (major version upgrade)
- Updated Framer Motion 10.16.4 → 12.23.26 (major version upgrade)
- Updated @types/react 18.2.33 → 19.2.7
- Updated @types/react-dom 18.2.14 → 19.2.3
- Added missing dependencies: ajv@8.17.1, react-icons@5.5.0, date-fns@4.1.0
- Fixed React 19 breaking change: Updated index.tsx to use createRoot API
- Removed old conflicting index.js file (had old ReactDOM.render)
- Fixed index.html by removing incorrect script tag
- Fixed Tailwind CSS styling by adding index.css import to index.tsx
- All changes tested and verified working on local dev server
- Committed to Git: 9b964ea, 38773db, 45d4ff3, 3b88fc8
- Pushed to remote repository - ready for production deployment
- All previously hardcoded data (board members, events, gallery, minutes, sponsors) now lives in MongoDB and is editable via the `/manage` dashboard
- Board members can log in at `/manage` using their `@deafmissoula.org` email address
- First login forces a password change; forgot-password flow sends reset link via email
- Dashboard supports drag-and-drop reordering for Minutes and Board Members tabs
- Image/file uploads go to a persistent Docker volume (`mcdi-uploads-data`)
- MongoDB runs in a separate container (`mcdi-mongo-data` volume)
2. SBS Solar Sponsor Addition (Oct 15, 2025):
- Added sbs-solar.png logo to client/public/sponsors/
- Updated Sponsors.tsx with SBS Solar information
- Included website, Facebook, and Instagram links
- Description highlights renewable energy and community engagement
- Committed to Git: 8d4c058
- Pushed to remote repository for deployment
## Board Member Accounts
1. Video Gallery with Thumbnails (Oct 14, 2025):
- Extracted thumbnails from all 6 videos at 5-second mark using ffmpeg
- Thumbnail sizes: 80-183 KB each (total ~740 KB)
- Updated VideoGallery.tsx to display thumbnail images instead of video elements
- Thumbnails show actual video content preview with play button overlay
- Improved loading performance - thumbnails load instantly
- Deleted entire Junk directory (cleaned up all source files)
- Committed to Git: 4f6bc08
- Successfully deployed to production
All four board members are seeded with accounts. Passwords are set via the `/manage` dashboard or the welcome email flow. On first login each member is forced to set their own password.
2. Previous: Video Gallery creation and deployment:
- Optimized 5 MOV videos from Junk directory using ffmpeg (H.264, CRF 28, 128k audio)
- Created community-01.mp4 through community-05.mp4 (total ~36 MB)
- Moved archived board intro video as board-intro-archive.mp4 (128 MB)
- Created VideoGallery.tsx component with modal video player
- Video player features: play/pause/stop controls, seek bar, auto-hiding controls
- Responsive grid layout (1-3 columns based on screen size)
- Added to navigation: "Video Gallery" in About MCDi dropdown and mobile menu
- Route added: /videos
- Fixed TypeScript errors with FaPlay icon (used size/color props, Tailwind opacity)
- Committed to Git: b28bafa
| Name | Email | Position |
|------|-------|----------|
| Rita Brandborg | rita.brandborg@deafmissoula.org | President |
| Skyla Wilson | skyla.wilson@deafmissoula.org | Vice President |
| Tessa Williams | tessa.williams@deafmissoula.org | Secretary |
| Aubz M | aubz.m@deafmissoula.org | Treasurer |
2. Previous: Gallery photo optimization and renaming:
- Renamed gallery photos to 0001.jpg through 0010.jpg format
- Converted 8 HEIC files from Junk directory to JPG using macOS sips tool
- Optimized all photos for web (resized to max 1920px, 80% quality)
- Removed all captions per owner request (photos display without text)
- Gallery shows 10 community event photos in responsive grid
- Committed to Git: a92839f
Admin fallback: `admin@deafmissoula.org` (password in `stack.env``ADMIN_PASSWORD`)
3. Previous: Created new Gallery component (Gallery.tsx):
- Responsive photo grid layout (1-4 columns based on screen size)
- Clickable images that enlarge in a modal viewer
- Smooth animations using Framer Motion
- Modal features: click outside to close, X button, spring animation
- Maintains website's color scheme (#6B1631 red)
- Added to navigation: "About MCDi" dropdown and mobile menu
- Route added: /gallery
## Current Deployment
2. Previous: Modified Header.tsx to display donation button on mobile:
- Added permanent yellow donation button below hamburger menu on mobile devices
- Button is always visible in the red header area (no need to open menu)
- Removed duplicate donation button from inside mobile menu dropdown
- Desktop view unchanged - button remains in top right corner
- Mobile header now has extended height to accommodate the button
- Button centered and properly styled with same appearance as desktop version
2. Previous: Added 5 new events to eventData.ts:
- December 13, 2025: Board Meeting at Liquid Planet at The Book Exchange (9-10 AM)
- March 14, 2026: Board Meeting at TBD location (9-10 AM)
- June 13, 2026: Board Meeting at TBD location (9-10 AM)
- September 12, 2026: Board Meeting at TBD location (9-10 AM)
- September 12, 2026: Annual Membership Meeting at TBD location (10-11 AM)
- All events use Tessa Williams as contact (tessa.williams@deafmissoula.org)
- Events will automatically appear in calendar view and upcoming events section
2. Updated intro video:
- Replaced mcdi-intro.mp4 with new version
- Original video saved as mcdi-intro-old.mp4
- Video component in Meet-Board.tsx already configured to work with new file
- No code changes needed - video path remains /videos/mcdi-intro.mp4
2. Previous completed work:
- Added new meeting minutes for January 11, 2025 and March 8, 2025
- Added 6.jpg to rotating photo carousel
- Fixed CACHEBUST deployment issue with stack.env configuration
- Updated email configuration for production server compatibility (port 2525)
- All previous changes successfully deployed to production
- **Production server:** `10.4.0.205` (WireGuard VPN)
- **Site path:** `/home/chaulmark/websites/deafmissoula.org/`
- **Containers:** `deafmissoulaorg-app-1` (port 801) + `deafmissoulaorg-mongodb-1`
- **Git branch:** `main` (v2), `v1` tag = last hardcoded version
## Next Steps
1. Future tasks on hold:
- Minutes update - waiting for approved minutes from secretary
3. Monitor video gallery performance on production
4. Check if board-intro-archive.mp4 (128 MB) loads acceptably
5. Future: Could add video descriptions or categories if requested
6. Future: Could add video duration display on thumbnails
## Solution Summary
- Problem: Portainer repository deployments require stack.env file in Git repo
- Solution: Created stack.env with CACHEBUST variable and updated docker-compose.yml
- Future: Update CACHEBUST value in stack.env and push to force rebuilds
- [ ] Onboard remaining board members (Skyla, Tessa, Aubz) once Rita demo is confirmed successful
- [ ] Change default `stack.env` passwords from placeholders to strong values
- [ ] Update events in dashboard when new meetings are scheduled (no more code changes needed for content updates)
- [ ] Minutes PDFs: new PDFs go in `client/public/minutes/` (git commit + deploy) OR upload via dashboard (no deploy needed)
## Deployment Process
To deploy changes to production server:
1. SSH to production server: `ssh chaulmark@10.4.0.205`
2. Navigate to website directory: `cd ~/websites/deafmissoula.org`
3. Pull latest changes: `git pull`
4. Rebuild Docker container: `docker-compose up -d --build`
## Deployment Command
## Technical Notes
- Website uses React with TypeScript
- Minutes component includes desktop/mobile viewing modes
- Sorting functionality maintained for all columns
- Minutes data ordered from newest (top) to oldest (bottom) in the source array
```bash
ssh chaulmark@10.4.0.205
cd ~/websites/deafmissoula.org
git pull origin main
docker compose down && docker compose up -d --build
```

View file

@ -1,15 +1,32 @@
# Product Context
## Purpose
The MCDI website serves as the online presence for MCD Inc., providing information about the organization, its meetings, and resources to the public.
The MCDi website is the public-facing online presence for Missoula Council of the Deaf, Inc. It serves the Deaf community in Missoula and surrounding areas with information about the organization, its board, events, meeting minutes, and community resources.
## Problems Solved
- Provides easy access to meeting minutes and organizational documents
- Enables transparency through public access to meeting records
- Facilitates communication between the organization and its members/public
## Intended Functionality
- Display meeting minutes in a sortable table format
- Allow viewing of PDF documents both on desktop (popup viewer) and mobile (direct download)
- Present organizational information and updates
- Maintain historical records of meetings and decisions
- Public access to meeting minutes, bylaws, and organizational documents
- Transparent governance through accessible records
- Community event calendar
- Photo and video gallery of MCDi events
- Directory of Montana and National Deaf resources
- Sponsor recognition
- Contact form for public inquiries
## Dashboard (`/manage`) Purpose
Allows board members to manage website content without requiring developer involvement:
- **Board Members tab:** Add/edit/remove board member profiles and headshots; drag to reorder
- **Events tab:** Add/edit/remove calendar events
- **Gallery tab:** Upload new photos; reorder the gallery
- **Minutes tab:** Upload new meeting minutes PDFs; drag to reorder
- **Sponsors tab:** Add/edit/remove sponsors and logos
- **Members tab:** Internal member directory (board use only)
## Intended Users
- **Public:** Read-only access to all public pages
- **Board members:** Login at `/manage` to update content; each member has their own account via their `@deafmissoula.org` Google Workspace email
- **Admin (Chris):** Full access to codebase, database, and admin account; can make structural changes via Claude Code

View file

@ -1,156 +1,43 @@
# Progress Status
## Completed Features
1. Resources Page:
- Comprehensive directory of Montana and National Deaf resources
- 9 organized categories: Advocacy, Legal Support, Family/Community, ASL Resources, Education, Employment, Interpreters, Community Organizations, Youth Programs
- 25+ organizations listed with full contact information
- Professional card-based design with contact icons (phone, email, address, website)
- Responsive 2-column layout (desktop) / single column (mobile)
- Smooth animations using Framer Motion
- Clickable website links that open in new tabs
- Clickable email and phone links for easy contact
- Added to navigation in About MCDi dropdown and mobile menu
- Route: /resources
- Successfully deployed to production
## v2 — Current (deployed May 27, 2026)
2. Sponsors Page:
- Four sponsors displayed: Drum Coffee, Imagine Nation Brewing, GILD Brewing, SBS Solar
- Each sponsor has logo, description, and social media links (website, Facebook, Instagram)
- Responsive grid layout (1-3 columns based on screen size)
- Clickable cards open modal with full description
- Route: /sponsors
- Successfully deployed to production
### Completed
2. Video Gallery:
- Created VideoGallery component with modal video player
- Optimized 5 community videos using ffmpeg (H.264, CRF 28)
- Added archived board intro video (128 MB)
- Custom video controls: play/pause/stop, seek bar, auto-hiding controls
- Responsive grid layout (1-3 columns)
- Added to navigation in About MCDi dropdown and mobile menu
- Route: /videos
- Successfully deployed to production
- **MongoDB backend** — All data (board, events, gallery, minutes, sponsors, members) moved from hardcoded arrays to MongoDB 7.0; Mongoose 8.x models with full CRUD API
- **`/manage` dashboard** — Board member login, all content tabs, drag-and-drop reorder for Minutes and Board tabs, image preview before upload
- **Board member auth** — Email-based login, argon2 hashing, JWT sessions, forced password change on first login, forgot/reset password via Google Workspace email, auto welcome email on new member creation
- **Rate limiting** — 5 login attempts per 5 minutes on `/api/auth/login`
- **File uploads** — multer with persistent Docker volume (`mcdi-uploads-data`); images and PDFs uploadable via dashboard
- **Docker** — Two-container setup: app + MongoDB; named volumes for data persistence; `caddy_network` external network so Caddy can route by container name
- **v1 rollback tag**`git tag v1` in Gitea points to last hardcoded version
2. Photo Gallery:
- Created Gallery component with modal image viewer
- Optimized 10 community photos (HEIC to JPG, max 1920px, 80% quality)
- Renamed photos to 0001.jpg through 0010.jpg format
- No captions per owner request
- Responsive grid layout (1-4 columns)
- Added to navigation in About MCDi dropdown and mobile menu
- Route: /gallery
- Successfully deployed to production
### Existing Features (carried over from v1)
3. Minutes Component:
- Sortable table implementation
- PDF viewer integration
- Mobile/desktop responsive design
- Meeting entries through March 8, 2025
- Responsive frontend — React 19, Tailwind CSS, Framer Motion
- Calendar with upcoming events
- Photo gallery (17 photos: 00010010 + 16 + IMG_1615)
- Video gallery with thumbnails (5 community videos + board intro archive)
- Board member profiles with headshots and bios
- Meeting minutes table with sortable columns and PDF viewer
- Bylaws PDFs
- Sponsors page with logos and social links
- Resources directory (Montana + National Deaf organizations)
- Contact form (Google Workspace SMTP)
- Mobile donation button
4. Calendar Events:
- Updated March 4, 2026: Removed past events, updated March 14 meeting with Funk It Coffee location
- Current events: March 14, June 13, and September 12, 2026 meetings
- Events automatically appear in calendar view
## What's Next
5. Mobile Donation Button:
- Permanent yellow button below hamburger menu on mobile
- Always visible in red header area
- [ ] Onboard board members to dashboard (starting with Rita Brandborg)
- [ ] Change default passwords in `stack.env`
- [ ] Future: Additional gallery photos can be added directly via dashboard (no deploy needed)
- [ ] Future: New meeting minutes can be uploaded via dashboard PDF upload (no deploy needed)
- [ ] Future: Consider pagination on minutes table if list grows large
## Recent Progress
1. **Mobile Menu Fix (March 10, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED
- Fixed mobile hamburger menu to include all items from desktop "About MCDi" dropdown
- Added missing navigation items: Meet MCDi Board, Bylaws, Minutes, Sponsors
- Mobile menu now shows complete parity with desktop (11 total items)
- Committed to Git: bfa0756
- Pushed to remote repository
- Deployed to production server (10.4.0.205)
- Docker container rebuilt with --no-cache flag
- Verified working on public website (HTTP 200)
## Git History Reference
2. **Resources Page (March 10, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED
- Created comprehensive Resources directory with Montana and National Deaf resources
- Organized into 9 categories covering advocacy, support, education, employment, and youth programs
- Professional card-based design with full contact information
- Responsive layout with smooth animations
- Added to navigation in About MCDi dropdown and mobile menu
- Committed to Git: added54
- Pushed to remote repository
- Deployed to production server (10.4.0.205)
- Docker container rebuilt successfully
- Verified working on public website (HTTP 200)
2. **Events Calendar Update (March 4, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED
- Removed 2 past events (January 11, 2025 and December 13, 2025)
- Updated March 14, 2026 Board Meeting with complete details:
* Location: Funk It Coffee and Thrift
* Address: 314 N 1st St. West, Missoula MT 59802
* Time: 10:00 AM - 11:00 AM (changed from 9:00 AM - 10:00 AM)
- Kept future events (June 2026 and September 2026 meetings)
- Committed to Git: 3eff1a9
- Pushed to remote repository
- Deployed to production server (10.4.0.205)
- Docker container rebuilt with --no-cache flag
- Verified working on public website (after browser cache clear)
- Changes are live and visible to public
2. **Web Package Updates (Dec 15, 2025)**: ✅ FULLY COMPLETED
- Updated all major web packages to latest versions
- React 18.2.0 → 19.2.3 (major version with new createRoot API)
- React Router 6.17.0 → 7.10.1 (major version upgrade)
- Framer Motion 10.16.4 → 12.23.26 (latest animation library)
- Updated TypeScript type definitions for React 19
- Added missing dependencies: ajv@8.17.1, react-icons@5.5.0, date-fns@4.1.0
- Fixed React 19 breaking changes (createRoot API in index.tsx)
- Removed old conflicting index.js file
- Fixed index.html by removing incorrect script tag
- Fixed Tailwind CSS styling by adding index.css import
- All changes tested and verified working on local dev server
- Build successful with only minor ESLint warnings
- Committed to Git: 9b964ea, 38773db, 45d4ff3, 3b88fc8
- Pushed to remote repository
- Ready for production Docker rebuild
2. SBS Solar Sponsor Addition (Oct 15, 2025):
- Added sbs-solar.png logo (70 KB) to client/public/sponsors/
- Updated Sponsors.tsx with SBS Solar entry
- Added website: https://www.sbssolar.com/
- Added Facebook: https://www.facebook.com/sbssolar
- Added Instagram: https://www.instagram.com/sbssolar/
- Description emphasizes renewable energy and community engagement
- Committed to Git: 8d4c058
- Pushed to remote repository
- Ready for Docker rebuild and deployment
2. Video Gallery Thumbnails (Oct 14, 2025):
- Extracted thumbnails from all 6 videos at 5-second mark using ffmpeg
- Created 6 thumbnail JPG files (80-183 KB each, total ~740 KB)
- Updated VideoGallery.tsx to use thumbnail images for instant preview
- Thumbnails display actual video content with play button overlay
- Significantly improved loading performance
- Deleted entire Junk directory (cleaned up all source files)
- Deployed successfully (commit 4f6bc08)
2. Video Gallery Implementation (Oct 14, 2025):
- Converted 5 MOV files to optimized MP4 (total ~36 MB)
- Created VideoGallery.tsx with same player design as Meet-Board
- Fixed TypeScript errors with FaPlay icon
- Deployed successfully (commit b28bafa)
3. Photo Gallery Updates (Oct 14, 2025):
- Renamed all gallery photos to numbered format (0001-0010)
- Removed captions from photo gallery
- Deployed successfully (commit a92839f)
## What's Left
1. On Hold (waiting for information):
- Minutes update - waiting for approved minutes from secretary
3. Future Considerations:
- Add more sponsors as partnerships develop
- Add video descriptions or categories if requested
- Add video duration display on thumbnails
- Consider optimizing board-intro-archive.mp4 if needed
- Monitor for additional meeting minutes
- Consider pagination if minutes list grows large
- Use stack.env CACHEBUST method for future forced deployments
| Tag/Commit | Description |
|-----------|-------------|
| `v1` | Last hardcoded version (no database) |
| `main` | Current v2 production branch |
| `development` | Merged into main May 27, 2026 — v2 development branch |

View file

@ -1,39 +1,88 @@
# System Patterns
## Architecture
- React-based frontend
- TypeScript for type safety
- Tailwind CSS for styling
- Docker containerization
- Portainer for deployment management
## Component Patterns
1. Minutes Component
- Table-based display with sorting
- Responsive design (desktop/mobile)
- PDF viewer integration
- State management with React hooks
Single-container Express app serves both the React SPA and the REST API. MongoDB runs in a separate container on an internal Docker network.
```
Browser
└─► Caddy (caddy_network)
└─► deafmissoulaorg-app-1:801
├─ GET /api/* → Express routes
├─ GET /uploads/* → multer upload volume
├─ GET /photos/*, /videos/*, etc. → client/build/ static assets
└─ GET * → client/build/index.html (SPA catch-all)
└─► deafmissoulaorg-mongodb-1:27017 (internal network only)
```
## Data Flow
All frontend data is fetched from the API at runtime — nothing is hardcoded in components. Each component does a `useEffect``fetch('/api/...')``useState` pattern.
## File Organization
```
client/
├── public/
│ ├── minutes/ # PDF files
│ └── ...
├── src/
│ ├── components/ # React components
│ └── ...
└── ...
client/src/components/
├── Manage.tsx # /manage dashboard (auth + all admin tabs)
├── App.tsx # Routing: /manage/* outside Header/Footer
├── Home.tsx # Landing page (fetches events)
├── Calendar.tsx # Events calendar (fetches events)
├── Meet-Board.tsx # Board member profiles (fetches /api/board)
├── Gallery.tsx # Photo gallery (fetches /api/gallery)
├── VideoGallery.tsx # Video gallery (hardcoded paths in client/public/videos/)
├── Minutes.tsx # Meeting minutes table (fetches /api/minutes)
├── Sponsors.tsx # Sponsors (fetches /api/sponsors)
├── Bylaws.tsx # Static PDF links (hardcoded paths in client/public/)
└── Resources.tsx # Montana/National Deaf resources directory (static)
server/src/
├── models/ # Mongoose schemas
│ ├── BoardMember.ts # Includes auth fields (password, mustChangePassword, resetToken)
│ ├── Event.ts
│ ├── GalleryItem.ts
│ ├── Minute.ts # Has 'order' field for drag-and-drop sorting
│ ├── Sponsor.ts
│ ├── Member.ts
│ └── User.ts # Admin-only account (not BoardMember)
├── routes/
│ ├── auth.ts # Login, forgot/reset password, change password
│ ├── board.ts # CRUD + reorder + welcome email on create
│ ├── minutes.ts # CRUD + reorder (new entries auto-insert at top)
│ ├── events.ts
│ ├── gallery.ts
│ ├── sponsors.ts
│ ├── members.ts
│ └── email.ts # Contact form
├── middleware/
│ ├── auth.ts # JWT verification, exports AuthRequest
│ ├── upload.ts # Multer config for headshots/photos/sponsors/minutes
│ └── errorHandler.ts # ZodError → 400, others → 500
├── db.ts # mongoose.connect()
├── index.ts # Express app setup, static serving, route mounting
└── seed.ts # One-time data population (idempotent — skips if data exists)
```
## Dashboard (`/manage`) Patterns
- **Route security:** `/manage` is not linked from public nav (obscurity) + JWT required for all write operations
- **Login:** Checks BoardMember by email first, then User (admin fallback)
- **Drag-and-drop:** `useDragReorder<T>` custom hook using HTML5 drag API + `useRef` for drag index tracking; calls `PATCH /api/[resource]/reorder` with `{ ids: string[] }` on drop
- **Image preview:** `URL.createObjectURL(file)` in `ImagePicker` component before upload
- **Forced password change:** `mustChangePassword: true` → server issues `tempToken` (1-hour) → client shows change-password form before dashboard loads
## Deployment Pattern
1. Code changes pushed to Gitea
2. Container rebuild triggered via Portainer
3. New version deployed automatically
## Key Technical Decisions
- PDF viewing:
- Desktop: In-page popup viewer
- Mobile: Direct file download
- Sorting: Client-side implementation
- State: Local component state (useState)
1. Code changes pushed to Gitea (`git push origin main`)
2. SSH to production server (`ssh chaulmark@10.4.0.205`)
3. `git pull origin main && docker compose down && docker compose up -d --build`
No Portainer. No webhooks. Manual pull + rebuild.
## Rollback Pattern
`v1` tag in Git points to the last hardcoded version (no database):
```bash
git checkout v1
docker compose down
docker compose up -d --build
```

View file

@ -1,95 +1,90 @@
# Technical Context
## Technologies Used
- Frontend:
- React
- TypeScript
- Tailwind CSS
- Framer Motion (animations)
- Infrastructure:
- Docker
- Portainer
- Gitea (version control)
## Stack (v2 — current)
## Development Setup
1. Source code:
- Repository hosted on Gitea
- TypeScript for type safety
- React components in client/src/components/
### Frontend
- **React 19** with TypeScript 5.8
- **Tailwind CSS** (via PostCSS, built into `src/tailwind.css`)
- **Framer Motion 12.x** for animations
- **React Router 7.x** for client-side routing
- Build tool: Create React App (`react-scripts 5.0.1`)
- Install note: `npm install --legacy-peer-deps` required (react-scripts peer dep conflict with TS 5.x)
2. Build/Deploy:
- Docker container
- Portainer for container management
- Automatic rebuilds on code changes
### Backend
- **Node.js 22 LTS**, **Express 5.1**, TypeScript 5.8
- **Mongoose 8.x** ODM → **MongoDB 7.0**
- **`jose` 5.x** for JWT signing/verification
- **`argon2`** for password hashing (native compilation — requires `python3 make g++` in Docker)
- **`multer` 1.4.5-lts.1** for file uploads
- **`nodemailer` 6.x** for email via Google Workspace SMTP
- **`express-rate-limit` 8.5.x** on auth routes (5 attempts / 5 min)
- **`zod` 3.x** for request validation
- Dev runner: `tsx watch`
## Technical Constraints
1. File Management:
- PDF files stored in client/public/minutes/
- Static file serving for documents
### Express 5 Breaking Change
`app.get('*', handler)` is **invalid** in Express 5. SPA catch-all must use:
```typescript
app.use((_req, res) => { res.sendFile(path.join(clientBuild, 'index.html')); });
```
2. Browser Compatibility:
- Desktop: PDF viewer in popup
- Mobile: Direct PDF download
## Infrastructure
3. Dependencies:
- React for UI components
- TypeScript for type checking
- Framer Motion for animations
- Tailwind for styling
### Docker (Production)
Two containers managed by `docker-compose.yml`:
- `deafmissoulaorg-app-1` — Node.js app (Express serves React build + API) on port 801
- `deafmissoulaorg-mongodb-1` — MongoDB 7.0, internal network only (not exposed)
## Production Server Configuration (10.4.0.205)
Named volumes:
- `mcdi-mongo-data` — MongoDB data
- `mcdi-uploads-data` — Dashboard file uploads
Networks:
- `caddy_network` (external) — shared with Caddy reverse proxy; app joins this so Caddy can route by container name
- `internal` (bridge) — app ↔ MongoDB only
### Caddy Reverse Proxy
- **Location:** `~/docker/caddy/config/Caddyfile`
- **Container Name:** `caddy`
- **Admin API:** `0.0.0.0:2019`
- **Production server:** `10.4.0.205` (WireGuard VPN)
- **Config location:** `~/docker/caddy/config/Caddyfile`
### DeafMissoula.org Configuration
- **Docker Container:** `deafmissoulaorg-app-1`
- **Internal Port:** 801
- **Docker Network:** Separate from Caddy (different network IDs)
- **Current IP:** 172.18.0.6 (as of March 4, 2026)
**Important:** Because Caddy and deafmissoula containers are on different Docker networks, the Caddyfile must use IP addresses instead of container names for routing.
### Caddyfile Entry:
Because the app joins `caddy_network`, Caddy can use the container name:
```
deafmissoula.org {
reverse_proxy 172.18.0.6:801
reverse_proxy deafmissoulaorg-app-1:801
}
```
### Common Issues & Solutions
If you see a 502 after a rebuild, check the container name hasn't changed:
```bash
docker ps --filter name=deafmissoula
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
```
#### Issue: Website returns 502 after Docker rebuild
**Cause:** Docker container IP address changed during rebuild
### Production Server CPU Note
The Proxmox VM running 10.4.0.205 has CPU type set to `host` (passthrough). This is required for MongoDB 7.0+ which needs AVX instructions. Do not change the VM CPU type back to `kvm64` or `Common KVM processor` — MongoDB will crash.
**Solution:**
1. Check current container IP:
```bash
ssh chaulmark@10.4.0.205 "docker inspect deafmissoulaorg-app-1 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'"
```
## Authentication
2. Update Caddyfile:
```bash
# Download Caddyfile
scp chaulmark@10.4.0.205:~/docker/caddy/config/Caddyfile /tmp/Caddyfile
- Board members log in at `/manage` with their `@deafmissoula.org` email
- First login: `mustChangePassword: true` → server returns `tempToken` (1-hour JWT) → client redirects to force-change screen
- Forgot password: generates `resetToken` + `resetTokenExpiry` (1 hour) on BoardMember, emails `${APP_URL}/manage/reset-password?token=xxx`
- New board member added via dashboard: auto-sends welcome email with 7-day reset link
- Admin fallback: `admin@deafmissoula.org` is a `User` model (not BoardMember), password set via `ADMIN_PASSWORD` in `stack.env`
# Edit locally to update IP address in deafmissoula.org section
## File Serving
# Upload back
scp /tmp/Caddyfile chaulmark@10.4.0.205:~/docker/caddy/config/Caddyfile
```
Two separate file trees:
1. **Static assets** (baked into Docker image from `client/public/`) served at root paths: `/photos/`, `/videos/`, `/minutes/`, `/headshots/`, `/sponsors/`, `/logos/`
2. **Dashboard uploads** (Docker volume `mcdi-uploads-data`) served at `/uploads/headshots/`, `/uploads/photos/`, `/uploads/sponsors/`, `/uploads/minutes/`
3. Reload Caddy:
```bash
ssh chaulmark@10.4.0.205 "docker exec caddy caddy reload --config /etc/caddy/Caddyfile"
```
## Deployment
4. Verify:
```bash
curl -I https://deafmissoula.org
```
```bash
ssh chaulmark@10.4.0.205
cd ~/websites/deafmissoula.org
git pull origin main
docker compose down && docker compose up -d --build
# First deploy only:
docker exec deafmissoulaorg-app-1 node dist/seed.js
```
#### Better Long-term Solution
Connect both Caddy and deafmissoula containers to the same Docker network to use container names instead of IP addresses, which would prevent this issue.
CACHEBUST in `stack.env` forces a full Docker layer cache bust when incremented. Update it if a rebuild is pulling stale cached layers.