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 | Layer | Technology |
- Dynamic UI components with React and Framer Motion for animations |-------|-----------|
- TypeScript for enhanced type safety and developer experience | Frontend | React 19, TypeScript 5.8, Tailwind CSS, Framer Motion |
- Express.js backend serving static React files and handling API requests | Backend | Node.js 22 LTS, Express 5.1, TypeScript 5.8 |
- Docker containerization for consistent development and deployment environments | Database | MongoDB 7.0 (Mongoose 8.x ODM) |
- Calendar functionality with upcoming events display | Auth | JWT (`jose` 5.x), `argon2` password hashing |
- About Us section with board member information | File uploads | `multer` 1.4.5-lts |
- Bylaws and Minutes sections for organizational transparency | Email | `nodemailer` (Google Workspace SMTP) |
- Sponsor showcase | Containerization | Docker, Docker Compose |
- Contact form in the footer | 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 ## API Routes
- `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
### 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 ## Dashboard
- `sendEmail.ts`: Email sending functionality for contact form
## 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 **Forgot password:** Sends a 1-hour reset link to the member's email via Google Workspace SMTP.
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`
## 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` ## Environment Variables (`stack.env`)
- Server-side development: `cd server && npm run dev`
- Build client: `cd client && npm run build`
- Build server: `cd server && npm run build`
## 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: ## Production Deployment
NODE_ENV=production
GOOGLE_EMAIL=your-email@gmail.com GOOGLE_APP_PASSWORD=your-app-password
4. Build and start the containers: Production server: `10.4.0.205` (WireGuard VPN required)
docker-compose up --build 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: **Note on Caddy:** The app container joins the external `caddy_network`, so Caddy can route to it by container name:
docker-compose down ```
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: ## Rollback to v1
docker-compose up -d
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 # Active Context
## Current Status (March 10, 2026) ## Current Status (May 27, 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
## Recent Changes **v2 Fully Deployed** — MongoDB backend live on production
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)
2. **Resources Page (March 10, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED 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.
- 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)
2. **Events Calendar Update (March 4, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED ## What Changed in v2
- 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
2. **Web Package Updates (Dec 15, 2025)**: ✅ FULLY COMPLETED - All previously hardcoded data (board members, events, gallery, minutes, sponsors) now lives in MongoDB and is editable via the `/manage` dashboard
- Updated React 18.2.0 → 19.2.3 (major version upgrade) - Board members can log in at `/manage` using their `@deafmissoula.org` email address
- Updated React Router 6.17.0 → 7.10.1 (major version upgrade) - First login forces a password change; forgot-password flow sends reset link via email
- Updated Framer Motion 10.16.4 → 12.23.26 (major version upgrade) - Dashboard supports drag-and-drop reordering for Minutes and Board Members tabs
- Updated @types/react 18.2.33 → 19.2.7 - Image/file uploads go to a persistent Docker volume (`mcdi-uploads-data`)
- Updated @types/react-dom 18.2.14 → 19.2.3 - MongoDB runs in a separate container (`mcdi-mongo-data` volume)
- 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
2. SBS Solar Sponsor Addition (Oct 15, 2025): ## Board Member Accounts
- 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
1. Video Gallery with Thumbnails (Oct 14, 2025): 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.
- 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
2. Previous: Video Gallery creation and deployment: | Name | Email | Position |
- 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) | Rita Brandborg | rita.brandborg@deafmissoula.org | President |
- Moved archived board intro video as board-intro-archive.mp4 (128 MB) | Skyla Wilson | skyla.wilson@deafmissoula.org | Vice President |
- Created VideoGallery.tsx component with modal video player | Tessa Williams | tessa.williams@deafmissoula.org | Secretary |
- Video player features: play/pause/stop controls, seek bar, auto-hiding controls | Aubz M | aubz.m@deafmissoula.org | Treasurer |
- 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
2. Previous: Gallery photo optimization and renaming: Admin fallback: `admin@deafmissoula.org` (password in `stack.env``ADMIN_PASSWORD`)
- 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
3. Previous: Created new Gallery component (Gallery.tsx): ## Current Deployment
- 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
2. Previous: Modified Header.tsx to display donation button on mobile: - **Production server:** `10.4.0.205` (WireGuard VPN)
- Added permanent yellow donation button below hamburger menu on mobile devices - **Site path:** `/home/chaulmark/websites/deafmissoula.org/`
- Button is always visible in the red header area (no need to open menu) - **Containers:** `deafmissoulaorg-app-1` (port 801) + `deafmissoulaorg-mongodb-1`
- Removed duplicate donation button from inside mobile menu dropdown - **Git branch:** `main` (v2), `v1` tag = last hardcoded version
- 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
## Next Steps ## 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 - [ ] Onboard remaining board members (Skyla, Tessa, Aubz) once Rita demo is confirmed successful
- Problem: Portainer repository deployments require stack.env file in Git repo - [ ] Change default `stack.env` passwords from placeholders to strong values
- Solution: Created stack.env with CACHEBUST variable and updated docker-compose.yml - [ ] Update events in dashboard when new meetings are scheduled (no more code changes needed for content updates)
- Future: Update CACHEBUST value in stack.env and push to force rebuilds - [ ] Minutes PDFs: new PDFs go in `client/public/minutes/` (git commit + deploy) OR upload via dashboard (no deploy needed)
## Deployment Process ## Deployment Command
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`
## Technical Notes ```bash
- Website uses React with TypeScript ssh chaulmark@10.4.0.205
- Minutes component includes desktop/mobile viewing modes cd ~/websites/deafmissoula.org
- Sorting functionality maintained for all columns git pull origin main
- Minutes data ordered from newest (top) to oldest (bottom) in the source array docker compose down && docker compose up -d --build
```

View file

@ -1,15 +1,32 @@
# Product Context # Product Context
## Purpose ## 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 ## 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 - Public access to meeting minutes, bylaws, and organizational documents
- Display meeting minutes in a sortable table format - Transparent governance through accessible records
- Allow viewing of PDF documents both on desktop (popup viewer) and mobile (direct download) - Community event calendar
- Present organizational information and updates - Photo and video gallery of MCDi events
- Maintain historical records of meetings and decisions - 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 # Progress Status
## Completed Features ## v2 — Current (deployed May 27, 2026)
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
2. Sponsors Page: ### Completed
- 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
2. Video Gallery: - **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
- Created VideoGallery component with modal video player - **`/manage` dashboard** — Board member login, all content tabs, drag-and-drop reorder for Minutes and Board tabs, image preview before upload
- Optimized 5 community videos using ffmpeg (H.264, CRF 28) - **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
- Added archived board intro video (128 MB) - **Rate limiting** — 5 login attempts per 5 minutes on `/api/auth/login`
- Custom video controls: play/pause/stop, seek bar, auto-hiding controls - **File uploads** — multer with persistent Docker volume (`mcdi-uploads-data`); images and PDFs uploadable via dashboard
- Responsive grid layout (1-3 columns) - **Docker** — Two-container setup: app + MongoDB; named volumes for data persistence; `caddy_network` external network so Caddy can route by container name
- Added to navigation in About MCDi dropdown and mobile menu - **v1 rollback tag**`git tag v1` in Gitea points to last hardcoded version
- Route: /videos
- Successfully deployed to production
2. Photo Gallery: ### Existing Features (carried over from v1)
- 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
3. Minutes Component: - Responsive frontend — React 19, Tailwind CSS, Framer Motion
- Sortable table implementation - Calendar with upcoming events
- PDF viewer integration - Photo gallery (17 photos: 00010010 + 16 + IMG_1615)
- Mobile/desktop responsive design - Video gallery with thumbnails (5 community videos + board intro archive)
- Meeting entries through March 8, 2025 - 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: ## What's Next
- 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
5. Mobile Donation Button: - [ ] Onboard board members to dashboard (starting with Rita Brandborg)
- Permanent yellow button below hamburger menu on mobile - [ ] Change default passwords in `stack.env`
- Always visible in red header area - [ ] 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 ## Git History Reference
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)
2. **Resources Page (March 10, 2026)**: ✅ FULLY DEPLOYED AND VERIFIED | Tag/Commit | Description |
- Created comprehensive Resources directory with Montana and National Deaf resources |-----------|-------------|
- Organized into 9 categories covering advocacy, support, education, employment, and youth programs | `v1` | Last hardcoded version (no database) |
- Professional card-based design with full contact information | `main` | Current v2 production branch |
- Responsive layout with smooth animations | `development` | Merged into main May 27, 2026 — v2 development branch |
- 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

View file

@ -1,39 +1,88 @@
# System Patterns # System Patterns
## Architecture ## Architecture
- React-based frontend
- TypeScript for type safety
- Tailwind CSS for styling
- Docker containerization
- Portainer for deployment management
## Component Patterns Single-container Express app serves both the React SPA and the REST API. MongoDB runs in a separate container on an internal Docker network.
1. Minutes Component
- Table-based display with sorting ```
- Responsive design (desktop/mobile) Browser
- PDF viewer integration └─► Caddy (caddy_network)
- State management with React hooks └─► 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 ## File Organization
``` ```
client/ client/src/components/
├── public/ ├── Manage.tsx # /manage dashboard (auth + all admin tabs)
│ ├── minutes/ # PDF files ├── App.tsx # Routing: /manage/* outside Header/Footer
│ └── ... ├── Home.tsx # Landing page (fetches events)
├── src/ ├── Calendar.tsx # Events calendar (fetches events)
│ ├── components/ # React components ├── 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 ## Deployment Pattern
1. Code changes pushed to Gitea
2. Container rebuild triggered via Portainer
3. New version deployed automatically
## Key Technical Decisions 1. Code changes pushed to Gitea (`git push origin main`)
- PDF viewing: 2. SSH to production server (`ssh chaulmark@10.4.0.205`)
- Desktop: In-page popup viewer 3. `git pull origin main && docker compose down && docker compose up -d --build`
- Mobile: Direct file download
- Sorting: Client-side implementation No Portainer. No webhooks. Manual pull + rebuild.
- State: Local component state (useState)
## 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 # Technical Context
## Technologies Used ## Stack (v2 — current)
- Frontend:
- React
- TypeScript
- Tailwind CSS
- Framer Motion (animations)
- Infrastructure:
- Docker
- Portainer
- Gitea (version control)
## Development Setup ### Frontend
1. Source code: - **React 19** with TypeScript 5.8
- Repository hosted on Gitea - **Tailwind CSS** (via PostCSS, built into `src/tailwind.css`)
- TypeScript for type safety - **Framer Motion 12.x** for animations
- React components in client/src/components/ - **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: ### Backend
- Docker container - **Node.js 22 LTS**, **Express 5.1**, TypeScript 5.8
- Portainer for container management - **Mongoose 8.x** ODM → **MongoDB 7.0**
- Automatic rebuilds on code changes - **`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 ### Express 5 Breaking Change
1. File Management: `app.get('*', handler)` is **invalid** in Express 5. SPA catch-all must use:
- PDF files stored in client/public/minutes/ ```typescript
- Static file serving for documents app.use((_req, res) => { res.sendFile(path.join(clientBuild, 'index.html')); });
```
2. Browser Compatibility: ## Infrastructure
- Desktop: PDF viewer in popup
- Mobile: Direct PDF download
3. Dependencies: ### Docker (Production)
- React for UI components Two containers managed by `docker-compose.yml`:
- TypeScript for type checking - `deafmissoulaorg-app-1` — Node.js app (Express serves React build + API) on port 801
- Framer Motion for animations - `deafmissoulaorg-mongodb-1` — MongoDB 7.0, internal network only (not exposed)
- Tailwind for styling
## 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 ### Caddy Reverse Proxy
- **Location:** `~/docker/caddy/config/Caddyfile` - **Production server:** `10.4.0.205` (WireGuard VPN)
- **Container Name:** `caddy` - **Config location:** `~/docker/caddy/config/Caddyfile`
- **Admin API:** `0.0.0.0:2019`
### DeafMissoula.org Configuration Because the app joins `caddy_network`, Caddy can use the container name:
- **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:
``` ```
deafmissoula.org { 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 ### Production Server CPU Note
**Cause:** Docker container IP address changed during rebuild 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:** ## Authentication
1. Check current container IP:
```bash
ssh chaulmark@10.4.0.205 "docker inspect deafmissoulaorg-app-1 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'"
```
2. Update Caddyfile: - Board members log in at `/manage` with their `@deafmissoula.org` email
```bash - First login: `mustChangePassword: true` → server returns `tempToken` (1-hour JWT) → client redirects to force-change screen
# Download Caddyfile - Forgot password: generates `resetToken` + `resetTokenExpiry` (1 hour) on BoardMember, emails `${APP_URL}/manage/reset-password?token=xxx`
scp chaulmark@10.4.0.205:~/docker/caddy/config/Caddyfile /tmp/Caddyfile - 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
# Upload back
scp /tmp/Caddyfile chaulmark@10.4.0.205:~/docker/caddy/config/Caddyfile
```
3. Reload Caddy: ## File Serving
```bash
ssh chaulmark@10.4.0.205 "docker exec caddy caddy reload --config /etc/caddy/Caddyfile"
```
4. Verify: Two separate file trees:
```bash 1. **Static assets** (baked into Docker image from `client/public/`) served at root paths: `/photos/`, `/videos/`, `/minutes/`, `/headshots/`, `/sponsors/`, `/logos/`
curl -I https://deafmissoula.org 2. **Dashboard uploads** (Docker volume `mcdi-uploads-data`) served at `/uploads/headshots/`, `/uploads/photos/`, `/uploads/sponsors/`, `/uploads/minutes/`
```
#### Better Long-term Solution ## Deployment
Connect both Caddy and deafmissoula containers to the same Docker network to use container names instead of IP addresses, which would prevent this issue.
```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
```
CACHEBUST in `stack.env` forces a full Docker layer cache bust when incremented. Update it if a rebuild is pulling stale cached layers.