Create shared getVehicleLabel/getVehicleSubtitle in core/utils with VehicleLike interface. Replace all direct year/make/model concatenation across 17 consumer files to prevent null values in vehicle names. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
161 lines
4.7 KiB
TypeScript
161 lines
4.7 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Button,
|
|
FormGroup,
|
|
FormControlLabel,
|
|
Checkbox,
|
|
Typography,
|
|
Alert,
|
|
Box,
|
|
} from '@mui/material';
|
|
import type { SubscriptionTier } from '../types/subscription.types';
|
|
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
|
|
|
|
interface Vehicle {
|
|
id: string;
|
|
make?: string;
|
|
model?: string;
|
|
year?: number;
|
|
nickname?: string;
|
|
}
|
|
|
|
interface VehicleSelectionDialogProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onConfirm: (selectedVehicleIds: string[]) => void;
|
|
vehicles: Vehicle[];
|
|
maxSelections: number;
|
|
targetTier: SubscriptionTier;
|
|
/** When true, dialog cannot be dismissed - user must make a selection */
|
|
blocking?: boolean;
|
|
}
|
|
|
|
export const VehicleSelectionDialog = ({
|
|
open,
|
|
onClose,
|
|
onConfirm,
|
|
vehicles,
|
|
maxSelections,
|
|
targetTier,
|
|
blocking = false,
|
|
}: VehicleSelectionDialogProps) => {
|
|
const [selectedVehicleIds, setSelectedVehicleIds] = useState<string[]>([]);
|
|
|
|
// Pre-select first N vehicles when dialog opens
|
|
useEffect(() => {
|
|
if (open && vehicles.length > 0) {
|
|
const initialSelection = vehicles.slice(0, maxSelections).map((v) => v.id);
|
|
setSelectedVehicleIds(initialSelection);
|
|
}
|
|
}, [open, vehicles, maxSelections]);
|
|
|
|
const handleToggle = (vehicleId: string) => {
|
|
setSelectedVehicleIds((prev) => {
|
|
if (prev.includes(vehicleId)) {
|
|
return prev.filter((id) => id !== vehicleId);
|
|
} else {
|
|
// Only add if under the limit
|
|
if (prev.length < maxSelections) {
|
|
return [...prev, vehicleId];
|
|
}
|
|
return prev;
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleConfirm = () => {
|
|
onConfirm(selectedVehicleIds);
|
|
};
|
|
|
|
|
|
const canConfirm = selectedVehicleIds.length > 0 && selectedVehicleIds.length <= maxSelections;
|
|
|
|
// Handle dialog close - prevent if blocking
|
|
const handleClose = (_event: object, reason: string) => {
|
|
if (blocking && (reason === 'backdropClick' || reason === 'escapeKeyDown')) {
|
|
return;
|
|
}
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onClose={handleClose}
|
|
maxWidth="sm"
|
|
fullWidth
|
|
disableEscapeKeyDown={blocking}
|
|
>
|
|
<DialogTitle>
|
|
{blocking ? 'Vehicle Selection Required' : 'Select Vehicles to Keep'}
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<Alert severity={blocking ? 'info' : 'warning'} sx={{ mb: 2 }}>
|
|
{blocking ? (
|
|
<>
|
|
Your subscription has been downgraded to the {targetTier} tier, which allows{' '}
|
|
{maxSelections} vehicle{maxSelections > 1 ? 's' : ''}. Please select which vehicles
|
|
you want to keep active to continue using the app. Unselected vehicles will be hidden
|
|
but not deleted, and you can unlock them by upgrading later.
|
|
</>
|
|
) : (
|
|
<>
|
|
You are downgrading to the {targetTier} tier, which allows {maxSelections} vehicle
|
|
{maxSelections > 1 ? 's' : ''}. Select which vehicles you want to keep active.
|
|
Unselected vehicles will be hidden but not deleted, and you can unlock them by
|
|
upgrading later.
|
|
</>
|
|
)}
|
|
</Alert>
|
|
|
|
<Box sx={{ mb: 2 }}>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Selected {selectedVehicleIds.length} of {maxSelections} allowed
|
|
</Typography>
|
|
</Box>
|
|
|
|
<FormGroup>
|
|
{vehicles.map((vehicle) => (
|
|
<FormControlLabel
|
|
key={vehicle.id}
|
|
control={
|
|
<Checkbox
|
|
checked={selectedVehicleIds.includes(vehicle.id)}
|
|
onChange={() => handleToggle(vehicle.id)}
|
|
disabled={
|
|
!selectedVehicleIds.includes(vehicle.id) &&
|
|
selectedVehicleIds.length >= maxSelections
|
|
}
|
|
/>
|
|
}
|
|
label={getVehicleLabel(vehicle)}
|
|
/>
|
|
))}
|
|
</FormGroup>
|
|
|
|
{selectedVehicleIds.length === 0 && (
|
|
<Alert severity="error" sx={{ mt: 2 }}>
|
|
You must select at least one vehicle.
|
|
</Alert>
|
|
)}
|
|
|
|
{selectedVehicleIds.length > maxSelections && (
|
|
<Alert severity="error" sx={{ mt: 2 }}>
|
|
You can only select up to {maxSelections} vehicle{maxSelections > 1 ? 's' : ''}.
|
|
</Alert>
|
|
)}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
{!blocking && <Button onClick={onClose}>Cancel</Button>}
|
|
<Button onClick={handleConfirm} variant="contained" disabled={!canConfirm}>
|
|
{blocking ? 'Confirm Selection' : 'Confirm Downgrade'}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
};
|