feat: Add tier-based vehicle limit enforcement (refs #23)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

Backend:
- Add VEHICLE_LIMITS configuration to feature-tiers.ts
- Add getVehicleLimit, canAddVehicle helper functions
- Implement transaction-based limit check with FOR UPDATE locking
- Add VehicleLimitExceededError and 403 TIER_REQUIRED response
- Add countByUserId to VehiclesRepository
- Add comprehensive tests for all limit logic

Frontend:
- Add getResourceLimit, isAtResourceLimit to useTierAccess hook
- Create VehicleLimitDialog component with mobile/desktop modes
- Add useVehicleLimitCheck shared hook for limit state
- Update VehiclesPage with limit checks and lock icon
- Update VehiclesMobileScreen with limit checks
- Add tests for VehicleLimitDialog

Implements vehicle limits per tier (Free: 2, Pro: 5, Enterprise: unlimited)
with race condition prevention and consistent UX across mobile/desktop.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-11 16:36:53 -06:00
parent dff743ca36
commit 20189a1d37
15 changed files with 1179 additions and 48 deletions

View File

@@ -0,0 +1,23 @@
/**
* @ai-summary Hook for checking vehicle limit and managing limit dialog
* @ai-context Shared between desktop and mobile vehicle pages
*/
import { useState } from 'react';
import { useTierAccess } from '../../../core/hooks/useTierAccess';
export const useVehicleLimitCheck = (vehicleCount: number) => {
const { tier, isAtResourceLimit, getResourceLimit } = useTierAccess();
const [showLimitDialog, setShowLimitDialog] = useState(false);
const isAtLimit = isAtResourceLimit('vehicles', vehicleCount);
const limit = getResourceLimit('vehicles');
return {
isAtLimit,
limit,
tier,
showLimitDialog,
setShowLimitDialog,
};
};