The staging workflow was not copying docker-compose.yml to the server,
causing configuration changes (like Stripe secrets) to not take effect.
Added rsync step to sync config, scripts, and compose files before
deployment, matching the production workflow behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add VITE_STRIPE_PUBLISHABLE_KEY to frontend Dockerfile build args
- Add VITE_STRIPE_PUBLISHABLE_KEY to docker-compose.yml build args
- Add :ro flag to backend Stripe secret volume mounts for consistency
- Update inject-secrets.sh with STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET
- Add Stripe secrets to staging.yaml workflow (build arg + inject step)
- Add Stripe secrets to production.yaml workflow (inject step)
Requires STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET secrets and
VITE_STRIPE_PUBLISHABLE_KEY variable to be configured in Gitea.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
API Endpoints (all authenticated):
- GET /api/subscriptions - current subscription status
- POST /api/subscriptions/checkout - create Stripe subscription
- POST /api/subscriptions/cancel - schedule cancellation at period end
- POST /api/subscriptions/reactivate - cancel pending cancellation
- PUT /api/subscriptions/payment-method - update payment method
- GET /api/subscriptions/invoices - billing history
Grace Period Job:
- Daily cron at 2:30 AM to check expired grace periods
- Downgrades to free tier when 30-day grace period expires
- Syncs tier to user_profiles.subscription_tier
Email Templates:
- payment_failed_immediate (first failure)
- payment_failed_7day (7 days before grace ends)
- payment_failed_1day (1 day before grace ends)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create 4 new tables: subscriptions, subscription_events, donations, tier_vehicle_selections
- Add StripeClient wrapper with createCustomer, createSubscription, cancelSubscription,
updatePaymentMethod, createPaymentIntent, constructWebhookEvent methods
- Implement SubscriptionsRepository with full CRUD and mapRow case conversion
- Add domain types for all subscription entities
- Install stripe npm package v20.2.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a user signs up but doesn't verify their email, clicking the Login
button on the landing page would either do nothing or get stuck in a
loading state. Now checks for pendingVerificationEmail in localStorage
(set during signup) and redirects to /verify-email instead of attempting
Auth0 login.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed icon button hover behavior to match VehicleCard pattern:
- Removed background color fills on hover (was primary.main/error.main)
- Icons now use default MUI IconButton gray ripple on hover
- Edit icons use text.secondary color (matches VehicleCard)
- Delete icons use error.main color (matches VehicleCard)
Affected files:
- DocumentsPage.tsx
- FuelLogsList.tsx
- MaintenanceRecordsList.tsx
- MaintenanceSchedulesList.tsx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added missing Edit icon button between Eye and Trash icons.
Clicking Edit opens EditDocumentDialog to modify the document.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Documents page: Convert from text buttons to icon buttons (Eye for
View Details, Trash for Delete), add card hover shadow effect,
convert to MUI components for consistency
- Fuel Logs: Add row hover background effect on list items
- Maintenance Records: Add card hover shadow effect
- Maintenance Schedules: Add card hover shadow effect
All changes follow the VehicleCard pattern with:
- Light gray shadow/elevation on hover with 0.2s transition
- Consistent icon button styling with mobile-responsive touch targets
- Proper MUI component usage throughout
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PostgreSQL DECIMAL columns return as strings from pg driver.
- Add Number() conversion for fuelUnits and costPerUnit in toEnhancedResponse()
- Add query invalidation for 'all' key to fix dynamic updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enhanced repository methods were incorrectly calling mapRow() which
converts snake_case to camelCase, but the service's toEnhancedResponse()
expects raw database rows with snake_case properties. This caused
"Invalid time value" errors when calling new Date(row.created_at).
Fixed methods:
- createEnhanced
- findByVehicleIdEnhanced
- findByUserIdEnhanced
- findByIdEnhanced
- getPreviousLogByOdometer
- getLatestLogForVehicle
- updateEnhanced
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace polling-based auth detection with event-based subscription
- Remove unnecessary 100ms delay on desktop (keep 50ms for mobile)
- Unify dashboard data fetching to prevent duplicate API calls
- Use Promise.all for parallel maintenance schedule fetching
Reduces dashboard load time from ~1.5s to <500ms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename "Open" button to "View Details" on desktop and mobile document lists
- Add hasDisplayableMetadata helper to check if document has metadata to display
- Conditionally render Details section only when metadata exists
- Prevents showing empty "Details" header for documents without metadata
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create ExpirationBadge component with 30-day warning and expired states
- Create DocumentCardMetadata component for type-specific field display
- Update DocumentsPage to show metadata and expiration badges on cards
- Update DocumentsMobileScreen with metadata and badges (mobile variant)
- Redesign DocumentDetailPage with side-by-side layout (desktop) and
stacked layout (mobile) showing full metadata panel
- Add 33 unit tests for new components
- Fix jest.config.ts testMatch pattern for test discovery
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add purchase price and purchase date display to vehicle detail page
- Fix form validation to handle NaN from empty number inputs using z.preprocess
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The VIN Number field incorrectly showed license plate when VIN was empty.
Now displays "Not provided" for missing VIN values, matching mobile behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove CostInterval type and TCOResponse interface from frontend types
- Remove insurance/registration cost fields from VehicleForm schema and UI
- Keep purchasePrice and purchaseDate fields on vehicle form
- Remove TCODisplay component from VehicleDetailPage
- Delete TCODisplay.tsx component file
- Remove getTCO method from vehicles API client
Legacy TCO fields moved to ownership-costs feature in #29.
Backend endpoint preserved for future reporting feature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace raw HTML checkboxes with MUI Checkbox wrapped in FormControlLabel
for consistent styling and theme integration across:
- DocumentForm.tsx (shared vehicles + scan maintenance checkboxes)
- VehicleForm.tsx (TCO enabled checkbox)
- SignupForm.tsx (terms acceptance checkbox)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The upload was hanging silently because breaking early from a
`for await` loop on a Node.js stream corrupts the stream's internal
state. The remaining stream could not be used afterward.
Changes:
- Collect ALL chunks from the file stream before processing
- Use subarray() for file type detection header (first 4100 bytes)
- Create single readable stream from complete buffer for storage
- Remove broken headerStream + remainingStream piping logic
This fixes the root cause where uploads would hang after logging
"Document upload requested" without ever completing or erroring.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document uploads were failing with "timeout of 10000ms exceeded" error
because the global axios client timeout (10s) was too short for
medium-sized files (1-5MB).
Added calculateUploadTimeout() function that calculates timeout based on
file size: 30s base + 10s per MB. This allows uploads to complete on
slower connections while still having reasonable timeout limits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Created DeleteDocumentConfirmDialog with context-aware messaging:
- Primary vehicle with no shares: Full delete
- Shared vehicle: Remove association only
- Primary vehicle with shares: Full delete (affects all)
- Integrated documents display in VehicleDetailPage records table
- Added delete button per document with 44px touch target
- Document deletion uses appropriate backend calls based on context
- Mobile-friendly dialog with responsive design
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implemented comprehensive document editing capabilities:
1. Created EditDocumentDialog component:
- Responsive MUI Dialog with fullScreen on mobile
- Wraps DocumentForm in edit mode
- Proper close handlers with refetch
2. Enhanced DocumentForm to support edit mode:
- Added mode prop ('create' | 'edit')
- Pre-populate all fields from initialValues
- Use useUpdateDocument hook when in edit mode
- Multi-select for shared vehicles (insurance only)
- Vehicle and document type disabled in edit mode
- Optional file upload in edit mode
- Dynamic button text (Create/Save Changes)
3. Updated DocumentDetailPage:
- Added Edit button with proper touch targets
- Integrated EditDocumentDialog
- Refetch document on successful edit
Mobile-first implementation:
- All touch targets >= 44px
- Dialog goes fullScreen on mobile
- Form fields stack on mobile
- Shared vehicle checkboxes have min-h-[44px]
- Buttons use flex-wrap for mobile overflow
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Created shared utility getVehicleLabel() for consistent vehicle display
- Updated DocumentsPage to show vehicle names with clickable links
- Added "Shared with X vehicles" indicator for multi-vehicle docs
- Updated DocumentDetailPage with vehicle name and shared vehicle list
- Updated DocumentsMobileScreen with vehicle names and "Shared" indicator
- All vehicle names link to vehicle detail pages
- Mobile-first with 44px touch targets on all links
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>