From b06a5e692b6521552f4d0f3f0bf9531e10aa82e9 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Sat, 24 Jan 2026 11:31:26 -0600 Subject: [PATCH] feat: integrate vehicle selection dialog on login (refs #60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add useNeedsVehicleSelection and useVehicles hooks in App.tsx - Show blocking VehicleSelectionDialog after auth gate ready - Call downgrade API on confirm to save vehicle selections - Invalidate queries after selection to proceed to app 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/App.tsx | 44 +++++++++++++++++++ .../subscription/api/subscription.api.ts | 5 ++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4e534b5..f66ea77 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -82,6 +82,9 @@ import { CreateVehicleRequest } from './features/vehicles/types/vehicles.types'; import { MobileSettingsScreen } from './features/settings/mobile/MobileSettingsScreen'; import { SecurityMobileScreen } from './features/settings/mobile/SecurityMobileScreen'; import { useNavigationStore, useUserStore } from './core/store'; +import { useNeedsVehicleSelection, useDowngrade } from './features/subscription/hooks/useSubscription'; +import { useVehicles } from './features/vehicles/hooks/useVehicles'; +import { VehicleSelectionDialog } from './features/subscription/components/VehicleSelectionDialog'; import { useDataSync } from './core/hooks/useDataSync'; import { MobileDebugPanel } from './core/debug/MobileDebugPanel'; import { MobileErrorBoundary } from './core/error-boundaries/MobileErrorBoundary'; @@ -319,6 +322,25 @@ function App() { // Initialize login notifications useLoginNotifications(); + // Vehicle selection check for auto-downgraded users + const needsVehicleSelectionQuery = useNeedsVehicleSelection(); + const vehiclesQuery = useVehicles(); + const downgrade = useDowngrade(); + const queryClient = useQueryClient(); + + // Handle vehicle selection confirmation + const handleVehicleSelectionConfirm = useCallback((selectedVehicleIds: string[]) => { + downgrade.mutate( + { targetTier: 'free', vehicleIdsToKeep: selectedVehicleIds }, + { + onSuccess: () => { + // Invalidate the needs-vehicle-selection query to clear the dialog + queryClient.invalidateQueries({ queryKey: ['needs-vehicle-selection'] }); + }, + } + ); + }, [downgrade, queryClient]); + // Enhanced navigation and user state management const { activeScreen, @@ -620,6 +642,28 @@ function App() { ); } + // Check if user needs to make vehicle selection after auto-downgrade + // This blocks the app until user selects which vehicles to keep + const needsVehicleSelection = needsVehicleSelectionQuery.data?.needsSelection; + const vehicleMaxAllowed = needsVehicleSelectionQuery.data?.maxAllowed ?? 2; + const vehicleList = vehiclesQuery.data ?? []; + + if (needsVehicleSelection && vehicleList.length > 0) { + return ( + + {}} // No-op - dialog is blocking + onConfirm={handleVehicleSelectionConfirm} + vehicles={vehicleList} + maxSelections={vehicleMaxAllowed} + targetTier="free" + /> + + ); + } + // Mobile app rendering if (mobileMode) { return ( diff --git a/frontend/src/features/subscription/api/subscription.api.ts b/frontend/src/features/subscription/api/subscription.api.ts index 06ea085..d5a9458 100644 --- a/frontend/src/features/subscription/api/subscription.api.ts +++ b/frontend/src/features/subscription/api/subscription.api.ts @@ -3,7 +3,10 @@ import type { CheckoutRequest, PaymentMethodUpdateRequest, DowngradeRequest, Nee export const subscriptionApi = { getSubscription: () => apiClient.get('/subscriptions'), - needsVehicleSelection: () => apiClient.get('/subscriptions/needs-vehicle-selection'), + needsVehicleSelection: async (): Promise => { + const response = await apiClient.get('/subscriptions/needs-vehicle-selection'); + return response.data; + }, checkout: (data: CheckoutRequest) => apiClient.post('/subscriptions/checkout', data), cancel: () => apiClient.post('/subscriptions/cancel'), reactivate: () => apiClient.post('/subscriptions/reactivate'),