chore: UX design audit cleanup and receipt flow improvements #186

Merged
egullickson merged 25 commits from issue-162-ux-design-audit-cleanup into main 2026-02-14 03:50:23 +00:00
23 changed files with 63 additions and 51 deletions
Showing only changes of commit 325cf08df0 - Show all commits

View File

@@ -13,6 +13,7 @@
- `src/App.tsx`, `src/main.tsx` — app entry.
- `src/features/*` — feature pages/components/hooks.
- `src/core/*` — auth, api, store, hooks, query config, utils.
- `src/core/utils/vehicleDisplay.ts` — shared vehicle display helpers: `getVehicleLabel()` (display name with fallback chain) and `getVehicleSubtitle()` (Year Make Model formatting).
- `src/shared-minimal/*` — shared UI components and theme.
## Mobile + Desktop (required)

View File

@@ -0,0 +1,27 @@
/** Vehicle-like object with minimal fields for display purposes */
export interface VehicleLike {
year?: number | null;
make?: string | null;
model?: string | null;
trimLevel?: string | null;
nickname?: string | null;
vin?: string | null;
id?: string | null;
}
/** Primary display name with fallback chain: nickname -> year/make/model -> VIN -> ID */
export const getVehicleLabel = (vehicle: VehicleLike | undefined): string => {
if (!vehicle) return 'Unknown Vehicle';
if (vehicle.nickname?.trim()) return vehicle.nickname.trim();
const parts = [vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean);
if (parts.length > 0) return parts.join(' ');
if (vehicle.vin) return vehicle.vin;
return vehicle.id ? `${vehicle.id.substring(0, 8)}...` : 'Unknown Vehicle';
};
/** Subtitle line: "Year Make Model" with null safety. Returns empty string if insufficient data. */
export const getVehicleSubtitle = (vehicle: VehicleLike | undefined): string => {
if (!vehicle) return '';
const parts = [vehicle.year?.toString(), vehicle.make, vehicle.model].filter(Boolean);
return parts.length >= 2 ? parts.join(' ') : '';
};

View File

@@ -24,6 +24,7 @@ import {
SubscriptionTier,
ListUsersParams,
} from '../types/admin.types';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
// Modal component for dialogs
interface ModalProps {
@@ -128,7 +129,7 @@ const UserVehiclesList: React.FC<{ auth0Sub: string; isOpen: boolean }> = ({ aut
<div className="space-y-1">
{data.vehicles.map((vehicle, idx) => (
<div key={idx} className="text-sm text-slate-600 dark:text-silverstone bg-slate-50 dark:bg-carbon px-2 py-1 rounded">
{vehicle.year} {vehicle.make} {vehicle.model}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</div>
))}
</div>

View File

@@ -9,6 +9,7 @@ import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded';
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded';
import ScheduleRoundedIcon from '@mui/icons-material/ScheduleRounded';
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
import { VehicleNeedingAttention } from '../types';
interface VehicleAttentionProps {
@@ -104,7 +105,7 @@ export const VehicleAttention: React.FC<VehicleAttentionProps> = ({ vehicles, on
mb: 0.5,
}}
>
{vehicle.nickname || `${vehicle.year} ${vehicle.make} ${vehicle.model}`}
{getVehicleLabel(vehicle)}
</Box>
<p className="text-sm text-slate-600 dark:text-titanio">
{vehicle.reason}

View File

@@ -12,7 +12,6 @@ Document management UI with maintenance manual extraction. Handles file uploads,
| `mobile/` | Mobile-specific document layout | Mobile UI |
| `pages/` | DocumentsPage, DocumentDetailPage | Page layout |
| `types/` | TypeScript type definitions | Type changes |
| `utils/` | Utility functions (vehicle label formatting) | Helper logic |
## Key Files

View File

@@ -10,7 +10,7 @@ import { AddDocumentDialog } from '../components/AddDocumentDialog';
import { ExpirationBadge } from '../components/ExpirationBadge';
import { DocumentCardMetadata } from '../components/DocumentCardMetadata';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { getVehicleLabel } from '../utils/vehicleLabel';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
export const DocumentsMobileScreen: React.FC = () => {
console.log('[DocumentsMobileScreen] Component initializing');

View File

@@ -12,7 +12,7 @@ import { EditDocumentDialog } from '../components/EditDocumentDialog';
import { ExpirationBadge } from '../components/ExpirationBadge';
import { DocumentCardMetadata } from '../components/DocumentCardMetadata';
import { useVehicle, useVehicles } from '../../vehicles/hooks/useVehicles';
import { getVehicleLabel } from '../utils/vehicleLabel';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
export const DocumentDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();

View File

@@ -21,7 +21,7 @@ import { ExpirationBadge } from '../components/ExpirationBadge';
import type { DocumentRecord } from '../types/documents.types';
import { DocumentCardMetadata } from '../components/DocumentCardMetadata';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { getVehicleLabel } from '../utils/vehicleLabel';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
export const DocumentsPage: React.FC = () => {
const { isAuthenticated, isLoading: authLoading, loginWithRedirect } = useAuth0();

View File

@@ -1,11 +0,0 @@
import type { Vehicle } from '../../vehicles/types/vehicles.types';
export const getVehicleLabel = (vehicle: Vehicle | undefined): string => {
if (!vehicle) return 'Unknown Vehicle';
if (vehicle.nickname?.trim()) return vehicle.nickname.trim();
const parts = [vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean);
const primary = parts.join(' ').trim();
if (primary.length > 0) return primary;
if (vehicle.vin?.length > 0) return vehicle.vin;
return vehicle.id.slice(0, 8) + '...';
};

View File

@@ -23,6 +23,7 @@ import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { useResolveAssociation } from '../hooks/usePendingAssociations';
import type { PendingVehicleAssociation } from '../types/email-ingestion.types';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface ResolveAssociationDialogProps {
open: boolean;
@@ -166,9 +167,7 @@ export const ResolveAssociationDialog: React.FC<ResolveAssociationDialogProps> =
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{vehicles.map((vehicle) => {
const isSelected = selectedVehicleId === vehicle.id;
const vehicleName = vehicle.nickname
|| [vehicle.year, vehicle.make, vehicle.model].filter(Boolean).join(' ')
|| 'Unnamed Vehicle';
const vehicleName = getVehicleLabel(vehicle);
return (
<Box

View File

@@ -36,6 +36,7 @@ import { SubtypeCheckboxGroup } from './SubtypeCheckboxGroup';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { documentsApi } from '../../documents/api/documents.api';
import type { Vehicle } from '../../vehicles/types/vehicles.types';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface MaintenanceRecordEditDialogProps {
open: boolean;
@@ -218,10 +219,7 @@ export const MaintenanceRecordEditDialog: React.FC<MaintenanceRecordEditDialogPr
disabled
value={(() => {
const vehicle = vehicles?.find((v: Vehicle) => v.id === record.vehicleId);
if (!vehicle) return 'Unknown Vehicle';
if (vehicle.nickname?.trim()) return vehicle.nickname.trim();
const parts = [vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean);
return parts.length > 0 ? parts.join(' ') : 'Vehicle';
return getVehicleLabel(vehicle);
})()}
helperText="Vehicle cannot be changed when editing"
/>

View File

@@ -46,6 +46,7 @@ import { useTierAccess } from '../../../core/hooks/useTierAccess';
import { UpgradeRequiredDialog } from '../../../shared-minimal/components/UpgradeRequiredDialog';
import { documentsApi } from '../../documents/api/documents.api';
import toast from 'react-hot-toast';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
const schema = z.object({
vehicle_id: z.string().uuid({ message: 'Please select a vehicle' }),
@@ -279,7 +280,7 @@ export const MaintenanceRecordForm: React.FC = () => {
{vehicles && vehicles.length > 0 ? (
vehicles.map((vehicle) => (
<MenuItem key={vehicle.id} value={vehicle.id}>
{vehicle.year} {vehicle.make} {vehicle.model}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</MenuItem>
))
) : (

View File

@@ -39,6 +39,7 @@ import {
import { SubtypeCheckboxGroup } from './SubtypeCheckboxGroup';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import type { Vehicle } from '../../vehicles/types/vehicles.types';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface MaintenanceScheduleEditDialogProps {
open: boolean;
@@ -206,10 +207,7 @@ export const MaintenanceScheduleEditDialog: React.FC<MaintenanceScheduleEditDial
disabled
value={(() => {
const vehicle = vehicles?.find((v: Vehicle) => v.id === schedule.vehicleId);
if (!vehicle) return 'Unknown Vehicle';
if (vehicle.nickname?.trim()) return vehicle.nickname.trim();
const parts = [vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean);
return parts.length > 0 ? parts.join(' ') : 'Vehicle';
return getVehicleLabel(vehicle);
})()}
helperText="Vehicle cannot be changed when editing"
/>

View File

@@ -42,6 +42,7 @@ import {
getCategoryDisplayName,
} from '../types/maintenance.types';
import toast from 'react-hot-toast';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
const schema = z
.object({
@@ -214,7 +215,7 @@ export const MaintenanceScheduleForm: React.FC = () => {
{vehicles && vehicles.length > 0 ? (
vehicles.map((vehicle) => (
<MenuItem key={vehicle.id} value={vehicle.id}>
{vehicle.year} {vehicle.make} {vehicle.model}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</MenuItem>
))
) : (

View File

@@ -16,6 +16,7 @@ import { MaintenanceScheduleForm } from '../components/MaintenanceScheduleForm';
import { MaintenanceSchedulesList } from '../components/MaintenanceSchedulesList';
import { MaintenanceScheduleEditDialog } from '../components/MaintenanceScheduleEditDialog';
import type { MaintenanceRecordResponse, UpdateMaintenanceRecordRequest, MaintenanceScheduleResponse, UpdateScheduleRequest } from '../types/maintenance.types';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
export const MaintenanceMobileScreen: React.FC = () => {
const queryClient = useQueryClient();
@@ -125,7 +126,7 @@ export const MaintenanceMobileScreen: React.FC = () => {
{vehicles && vehicles.length > 0 ? (
vehicles.map((vehicle) => (
<MenuItem key={vehicle.id} value={vehicle.id} sx={{ minHeight: 44 }}>
{vehicle.year} {vehicle.make} {vehicle.model}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</MenuItem>
))
) : (

View File

@@ -16,6 +16,7 @@ import { useMaintenanceRecords } from '../hooks/useMaintenanceRecords';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { FormSuspense } from '../../../components/SuspenseWrappers';
import type { MaintenanceRecordResponse, UpdateMaintenanceRecordRequest, MaintenanceScheduleResponse, UpdateScheduleRequest } from '../types/maintenance.types';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
export const MaintenancePage: React.FC = () => {
const { data: vehicles, isLoading: isLoadingVehicles } = useVehicles();
@@ -156,7 +157,7 @@ export const MaintenancePage: React.FC = () => {
{vehicles && vehicles.length > 0 ? (
vehicles.map((vehicle) => (
<MenuItem key={vehicle.id} value={vehicle.id}>
{vehicle.year} {vehicle.make} {vehicle.model}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</MenuItem>
))
) : (

View File

@@ -7,6 +7,7 @@ import { useSettings } from '../hooks/useSettings';
import { useProfile, useUpdateProfile } from '../hooks/useProfile';
import { useExportUserData } from '../hooks/useExportUserData';
import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
import { useSubscription } from '../../subscription/hooks/useSubscription';
import { useAdminAccess } from '../../../core/auth/useAdminAccess';
import { useNavigationStore } from '../../../core/store';
@@ -373,7 +374,7 @@ export const MobileSettingsScreen: React.FC = () => {
className="p-3 bg-slate-50 dark:bg-nero rounded-lg"
>
<p className="font-medium text-slate-800 dark:text-avus">
{vehicle.year} {vehicle.make} {vehicle.model}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</p>
{vehicle.nickname && (
<p className="text-sm text-slate-500 dark:text-titanio">{vehicle.nickname}</p>

View File

@@ -13,6 +13,7 @@ import {
Box,
} from '@mui/material';
import type { SubscriptionTier } from '../types/subscription.types';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface Vehicle {
id: string;
@@ -70,13 +71,6 @@ export const VehicleSelectionDialog = ({
onConfirm(selectedVehicleIds);
};
const getVehicleLabel = (vehicle: Vehicle): string => {
if (vehicle.nickname) {
return vehicle.nickname;
}
const parts = [vehicle.year, vehicle.make, vehicle.model].filter(Boolean);
return parts.join(' ') || 'Unknown Vehicle';
};
const canConfirm = selectedVehicleIds.length > 0 && selectedVehicleIds.length <= maxSelections;

View File

@@ -9,6 +9,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
import { Vehicle } from '../types/vehicles.types';
import { useUnits } from '../../../core/units/UnitsContext';
import { VehicleImage } from './VehicleImage';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface VehicleCardProps {
vehicle: Vehicle;
@@ -24,8 +25,7 @@ export const VehicleCard: React.FC<VehicleCardProps> = ({
onSelect,
}) => {
const { formatDistance } = useUnits();
const displayName = vehicle.nickname ||
[vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean).join(' ');
const displayName = getVehicleLabel(vehicle);
return (
<Card

View File

@@ -12,6 +12,7 @@ import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog'
import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api';
import { VehicleImage } from '../components/VehicleImage';
import { OwnershipCostsList } from '../../ownership-costs';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface VehicleDetailMobileProps {
vehicle: Vehicle;
@@ -38,8 +39,7 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
onLogFuel,
onEdit
}) => {
const displayName = vehicle.nickname ||
[vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean).join(' ') || 'Vehicle';
const displayName = getVehicleLabel(vehicle);
const displayModel = vehicle.model || 'Unknown Model';
const [recordFilter, setRecordFilter] = useState<'All' | 'Fuel Logs' | 'Maintenance' | 'Documents'>('All');

View File

@@ -6,6 +6,7 @@ import React from 'react';
import { Card, CardActionArea, Box, Typography } from '@mui/material';
import { Vehicle } from '../types/vehicles.types';
import { VehicleImage } from '../components/VehicleImage';
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
interface VehicleMobileCardProps {
vehicle: Vehicle;
@@ -18,8 +19,7 @@ export const VehicleMobileCard: React.FC<VehicleMobileCardProps> = ({
onClick,
compact = false
}) => {
const displayName = vehicle.nickname ||
[vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean).join(' ') || 'Vehicle';
const displayName = getVehicleLabel(vehicle);
const displayModel = vehicle.model || 'Unknown Model';
return (

View File

@@ -12,6 +12,7 @@ import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
import BuildIcon from '@mui/icons-material/Build';
import DeleteIcon from '@mui/icons-material/Delete';
import { Vehicle } from '../types/vehicles.types';
import { getVehicleLabel, getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
import { vehiclesApi } from '../api/vehicles.api';
import { Card } from '../../../shared-minimal/components/Card';
import { VehicleForm } from '../components/VehicleForm';
@@ -224,8 +225,7 @@ export const VehicleDetailPage: React.FC = () => {
);
}
const displayName = vehicle.nickname ||
[vehicle.year, vehicle.make, vehicle.model, vehicle.trimLevel].filter(Boolean).join(' ') || 'Vehicle';
const displayName = getVehicleLabel(vehicle);
const handleRowClick = (recId: string, type: VehicleRecord['type']) => {
if (type === 'Fuel Logs') {
@@ -373,8 +373,7 @@ export const VehicleDetailPage: React.FC = () => {
Vehicle Details
</Typography>
<Typography variant="body2" color="text.secondary">
{vehicle.year} {vehicle.make} {vehicle.model}
{vehicle.trimLevel && ` ${vehicle.trimLevel}`}
{getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
</Typography>
{vehicle.vin && (
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>

View File

@@ -11,6 +11,7 @@ import { useAdminAccess } from '../core/auth/useAdminAccess';
import { useProfile, useUpdateProfile } from '../features/settings/hooks/useProfile';
import { useExportUserData } from '../features/settings/hooks/useExportUserData';
import { useVehicles } from '../features/vehicles/hooks/useVehicles';
import { getVehicleSubtitle } from '@/core/utils/vehicleDisplay';
import { useSubscription } from '../features/subscription/hooks/useSubscription';
import { useTheme } from '../shared-minimal/theme/ThemeContext';
import { DeleteAccountDialog } from '../features/settings/components/DeleteAccountDialog';
@@ -375,7 +376,7 @@ export const SettingsPage: React.FC = () => {
{index > 0 && <Divider />}
<ListItem sx={{ py: 1.5 }}>
<ListItemText
primary={`${vehicle.year} ${vehicle.make} ${vehicle.model}`}
primary={getVehicleSubtitle(vehicle) || 'Unknown Vehicle'}
secondary={vehicle.nickname || undefined}
primaryTypographyProps={{ fontWeight: 500 }}
/>