Fix highlighting display in emailed notes and add clear formatting hints to email/download buttons.
Changes:
- Added proper HTML/CSS structure to email template for rich text support
- Added CSS styles for mark (highlight), strong, em, u, s, headings, and lists
- Highlight now renders with yellow background (#fef08a) in emails
- All rich text formatting now properly displays in email clients
- Added formatting hints to buttons: "Email Notes (Formatting included)" and "Download Notes (No formatting)"
- Button hints use smaller text with opacity for subtle visual hierarchy
Email template improvements:
- Proper DOCTYPE and HTML structure
- Style block in head for rich text elements
- Removed white-space: pre-wrap from notes div to allow HTML rendering
- Maintained all existing sermon content styling
This ensures users understand that:
- Email preserves all rich text formatting (bold, italic, highlights, lists, etc.)
- Download converts to plain text for universal compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add intelligent auto-archiving system that automatically moves sermons to the "Previous Sermons" list when they are 1 day past their most recent date.
Features:
- Auto-archive logic that checks both primary and additional sermon dates
- Finds the most recent date across all dates for a sermon
- Archives sermon 1 day after the most recent date has passed
- Manual trigger via "Run Auto-Archive Now" button on admin page
- Automatic daily execution via scheduled cleanup task
- Clear admin UI with explanatory text and status messages
- Manual archive/unarchive functionality preserved
Implementation:
- Added getMostRecentSermonDate() helper to find latest date from primary and additional dates
- Added autoArchiveOldSermons() function to database utils
- Created /api/sermons/auto-archive endpoint for manual triggering
- Integrated into daily cleanup plugin schedule
- Updated admin UI with auto-archive button and status indicators
- Added unarchiveSermon() function for completeness
The system runs automatically every 24 hours and can be manually triggered by admins. Sermons are moved to the previous sermons dropdown on the home page exactly 1 day after their final presentation date, ensuring the main page always shows current and upcoming content while preserving access to past sermons.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement comprehensive rich text editing capabilities using Tiptap editor with full formatting toolbar and functionality.
Features:
- Bold, italic, underline, strikethrough text formatting
- Highlight text with yellow marker
- Bullet and numbered lists
- Heading styles (H2, H3)
- Text alignment (left, center, right)
- Undo/redo support
- Clear formatting option
- Intuitive toolbar with icons and tooltips
- Responsive font size support
Technical changes:
- Added Tiptap dependencies to package.json (@tiptap/vue-3, starter-kit, extensions)
- Created RichTextEditor component with full toolbar and formatting options
- Integrated editor into sermon notes section replacing textarea
- Updated download API to convert HTML to formatted plain text
- Updated email API to handle HTML content properly
- Notes now stored as HTML with rich formatting preserved
The editor provides a professional writing experience with all standard formatting tools while maintaining automatic save functionality and seamless integration with email/download features.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement intelligent menu filtering that hides self-referencing links and shows all applicable pages based on user role and authentication state.
Changes:
- Menu component now uses currentPath to filter out the current page link
- Added computed properties to detect which page user is on
- Fixed profile, admin, and users pages to dynamically detect admin status from API
- Added menu to forgot-password page for consistent navigation
- All pages now pass correct authentication state to Menu component
This ensures menus always show relevant navigation options while avoiding redundant links to the current page. Admin users now see all admin options (Manage Sermons, Manage Users) regardless of which page they're on, except the current one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add consistent header with logo and hamburger menu to authentication pages, matching the navigation pattern used throughout the site. The menu adapts to authentication state and provides easy access to home and other sections.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed logout to use window.location.href for full page reload
- Ensures all components refresh and show correct unauthenticated state
- Fixes issue where logout appeared to do nothing until manual refresh
- Menu, notes section, and all auth-dependent UI now update immediately
Previously, using navigateTo() to the same page didn't trigger a refresh,
leaving stale authenticated UI visible. Full page reload ensures clean state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Auth middleware now includes redirect parameter when sending to login
- Users attempting to access protected pages are redirected back after login
- Works seamlessly with logout flow: logout from /admin → login → back to /admin
- Ensures consistent behavior across all protected routes
This fixes the issue where logging out from admin pages would redirect to
login but not preserve the original page, preventing users from returning
to their admin workflow after re-authentication.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Updated logout handler to return user to current page instead of login
- Consistent with login behavior which redirects back to original page
- Allows users to continue browsing after logout without forced redirect
- Users who accidentally click logout can easily log back in and continue
This improves UX by keeping users on the page they were viewing,
rather than forcing them to the login page when they may have just
wanted to log out temporarily.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Renamed MobileMenu to Menu component (no longer mobile-only)
- Added 500ms debounce to prevent accidental double-tap menu toggles
- Improved click-outside detection using ref-based containment check
- Removed mobile/desktop navigation split - menu now consistent everywhere
- All pages now use single hamburger menu on both mobile and desktop
- Simplified header layouts across index, sermon, profile, admin, and users pages
This provides a cleaner, more consistent UX with the hamburger menu available
on all screen sizes. The debounce prevents the menu from closing accidentally
when navigating between pages or double-tapping.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Updated profile.vue to use MobileMenu on mobile, desktop buttons on desktop
- Updated admin.vue to use MobileMenu on mobile, desktop buttons on desktop
- Updated users.vue to use MobileMenu on mobile, desktop buttons on desktop
- All pages with header navigation now have consistent mobile UX
- Mobile menu provides clean, organized navigation with all options
- Desktop retains traditional button layout for familiarity
This ensures consistent navigation experience across the entire application,
with the hamburger menu appearing on all pages with headers when viewed
on mobile devices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Initialize isRegistering state from URL query parameter (mode=register)
- Update URL when toggling between login/register modes for shareable links
- Remove duplicate useRoute() calls in login/register handlers
- Ensures all Register buttons throughout the app show the registration form
This fixes the issue where Register links navigated to /login?mode=register
but the login page ignored the mode parameter and showed the login form instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented comprehensive navigation improvements to make registration
more discoverable and provide a cleaner mobile experience.
## Mobile Improvements
- Created reusable MobileMenu component with hamburger icon
- Replaces cluttered button layout with clean dropdown menu
- Includes Home, Profile, Admin links, and authentication options
- Auto-closes when clicking outside or navigating
- Smooth transition animations
## Register Button Added
- Sermon page: Added Register button next to Login in notes section
- Sermon page: Added Register button in header for non-authenticated users
- Home page: Added Register button next to Login for easy discovery
- All Register links redirect to login page in register mode
## Navigation Consistency
- Home button now visible on sermon pages (desktop and mobile)
- Consistent navigation experience across all pages
- Mobile menu available on both home and sermon pages
- Better mobile UX with less header crowding
Benefits:
- Users can easily find how to register
- Cleaner mobile interface
- Consistent navigation patterns
- Better discoverability of account features
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed SQLITE_READONLY errors by implementing proper volume permission
handling in Docker container. The issue occurred because Docker volumes
are mounted with root ownership, preventing the non-root nuxt user from
writing to the database.
Solution:
- Added docker-entrypoint.sh script to handle permission fixes
- Container starts as root to fix /app/data permissions
- Uses su-exec to drop privileges to nuxt user after fixing permissions
- Maintains security by running application as non-root user
This allows the SQLite database to be properly created and modified
while keeping the container secure with non-root execution.
Fixes:
- "attempt to write a readonly database" errors
- Session cleanup failures
- Rate limit cleanup failures
- Password reset cleanup failures
- Admin credential generation on first launch
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed Dockerfile to conditionally use npm ci or npm install
depending on whether package-lock.json exists. This makes the
build more flexible while still preferring npm ci for
reproducibility when a lock file is available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented all 5 critical efficiency improvements to optimize
performance, reduce resource usage, and improve scalability.
## 1. Database Indexes
- Added indexes on sermon_notes foreign keys (user_id, sermon_id)
- Added composite index on sermons (archived, date DESC)
- Added indexes on frequently queried columns across all tables
- Impact: Faster queries as data grows, better JOIN performance
## 2. Eliminated N+1 Query Pattern
- Reduced 2 API calls to 1 on home page load
- Changed from separate active/archived fetches to single call
- Filter archived sermons client-side using computed properties
- Impact: 50% reduction in HTTP requests per page load
## 3. Scheduled Database Cleanup
- Extended existing plugin to clean expired sessions hourly
- Added cleanup for expired rate limits every hour
- Added cleanup for expired password reset codes every hour
- Sermon cleanup continues to run daily based on retention policy
- Impact: Prevents database table growth, better performance
## 4. Multi-stage Docker Build
- Implemented 3-stage build: deps -> builder -> runtime
- Separated build-time and runtime dependencies
- Added non-root user (nuxt:nodejs) for security
- Integrated dumb-init for proper signal handling
- Added health check endpoint at /api/health
- Impact: Smaller image size, faster deployments, better security
## 5. HTTP Caching
- Static assets: 1 year cache (immutable)
- Logos/images: 1 year cache (immutable)
- API routes: No cache (always fresh)
- HTML pages: 10 minute cache with revalidation
- Impact: Reduced bandwidth, faster page loads, less server load
All optimizations follow best practices and maintain backward
compatibility with existing functionality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When an admin manually unlocks an account, both the account-level
lockout and all IP-based rate limits for the login endpoint are now
cleared. This ensures legitimate users can immediately attempt to
login after being unlocked, without being blocked by stale rate
limit cache entries.
Changes:
- Added clearAllRateLimitsForEndpoint() function to database utils
- Modified unlock endpoint to clear login rate limits after unlocking
- Updated success message to reflect rate limit clearing
- Enhanced logging to track rate limit clearing operations
Fixes issue where users would see "Too many login attempts" message
even with correct credentials after admin unlock, due to persistent
IP rate limit cache from previous failed attempts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed inconsistency between IP-based rate limiting and per-account lockout.
Previously, users would hit the IP rate limit at 5 attempts (15 min lockout)
but the account wouldn't be marked as locked until 10 attempts (30 min).
This caused confusion in the admin UI where locked accounts wouldn't show
the unlock button until 10 attempts were reached.
Changes:
- Reduced account lockout threshold from 10 to 5 failed attempts
- Reduced account lockout duration from 30 to 15 minutes
- Updated error message to reflect 15 minute lockout period
- Added detailed logging when account gets locked
- Updated README documentation to reflect correct limits
Both protection layers now work in harmony:
- IP-based rate limiting: 5 attempts = 15 min lockout
- Per-account lockout: 5 attempts = 15 min lock
This ensures the admin UI accurately shows account lock status and provides
the unlock option as soon as users hit the lockout threshold.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix for password reset flow. The frontend form was still configured
for 6-digit numeric codes while the backend was updated to use 8-character
alphanumeric codes.
Changes:
- Updated maxlength from 6 to 8 characters
- Changed pattern from [0-9]{6} to [0-9A-Za-z]{8}
- Updated placeholder from "000000" to "ABC123XY"
- Changed label from "6-Digit Code" to "Reset Code"
- Updated help text to say "8-character code"
- Added uppercase styling for better UX
- Changed button validation from code.length !== 6 to !== 8
This ensures users can enter the full reset code sent via email.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Simplified email configuration to always use process.env directly instead of
Nuxt runtime config. This ensures Docker environment variables are properly
read at runtime rather than being baked in at build time.
Changes:
- Removed Nuxt runtime config dependency from getEmailConfig()
- Always read EMAIL_* environment variables directly from process.env
- Added comprehensive debug logging to diagnose configuration issues
- Updated nuxt.config.ts with better documentation of runtime config behavior
This ensures environment variables set in docker-compose.yml are properly
used by the application at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed an issue where SMTP configuration would fall back to defaults despite
environment variables being set in docker-compose.yml. The email utility now
properly accesses runtime configuration by accepting the H3 event context.
Changes:
- Created getEmailConfig() helper with dual-strategy config access
- Pass event context from API handlers to email functions
- Added fallback to direct process.env access for reliability
- Added debug logging to diagnose configuration issues in production
This ensures Office365 and other SMTP providers work correctly when configured
via environment variables.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Add items-start to status column flex container
- Ensures status badges (Active, Locked, Failed attempts) align to the left
- Consistent with Role badge alignment above
- Improves visual consistency in the table layout
Before: Status badges were centered/stretched in the column
After: Status badges are left-aligned like other badges
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- docker-compose.yml: Change from external volume to managed volume
- Volume name: nlcc-data → data
- Docker Compose auto-prefixes: nlcc-itinerary_data
- Remove external: true flag
- Volume created automatically on first up
- README.md: Update all volume references
- Installation: Remove manual volume creation step
- Volume name: nlcc-data → nlcc-itinerary_data
- Add note about automatic creation
- Update all backup/restore/inspect commands
Benefits:
- No manual volume creation required
- Docker Compose manages lifecycle automatically
- Project-scoped volume naming (nlcc-itinerary_data)
- Cleaner deployment process (one less step)
- Volume automatically created on docker-compose up
- Consistent with Docker Compose best practices
The volume will be created as "nlcc-itinerary_data" where:
- nlcc-itinerary = project directory name
- data = volume name defined in docker-compose.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Replace bind mount (./data) with external named volume (nlcc-data)
- Volume must be created before first run: docker volume create nlcc-data
- Improves portability and follows Docker best practices
- Better separation between code and data
Benefits:
- Data persists across container rebuilds and updates
- Easier backup and restore operations
- Platform-agnostic (works same on Linux/Windows/macOS)
- Managed by Docker's volume system
- No permission issues with bind mounts
README Updates:
- Added volume creation step to installation instructions
- Documented volume management commands (create, inspect, backup, restore)
- Added backup/restore examples using alpine container
- Clarified data persistence behavior
Note: Existing deployments using ./data bind mount will need to:
1. Backup existing data: cp -r ./data ./data-backup
2. Create volume: docker volume create nlcc-data
3. Restart container: docker-compose up -d
4. Copy data to volume if needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed image reference from local tag to GitLab container registry at
glcr.rydertech.us/ryder/nlcc-itinerary:latest
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed build configuration from docker-compose.yml. The image should be built
separately using 'docker build -t nlcc-itinerary:latest .' and the compose file
now just references the pre-built image with runtime configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed behavior so that editing a sermon no longer redirects to homepage.
Instead, it shows a success message and keeps the form filled for further edits.
Creating a new sermon still redirects to homepage as before.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented a configurable retention policy system for sermons with automatic cleanup:
- Added settings table to store retention policy configuration
- Created API endpoints for getting/setting retention policy
- Added Database Settings section to admin page with retention options (forever, 1-10 years)
- Implemented manual cleanup endpoint for on-demand deletion
- Added automated daily cleanup task via Nitro plugin
- Sermons are deleted based on their date field according to the retention policy
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>