feat(web): vehicle detail polish — analytics, chatwoot cleanup, skeleton, heading
- Capture category_view_changed and catalog_search_opened so the funnel picks up category-view and cross-tree search activation (previously uninstrumented). - Expose clearChatwootVehicle() and call it from the route's effect cleanup so a later support chat doesn't carry the previously-viewed car's VIN/brand. - Match the loading skeleton shape to the active viewMode — render a card grid for grid mode (was always 8 row-bars). - Switch the vehicle title to the display font (font-display, tracking-tight) for typography parity with the rest of the app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,9 @@ import { CategoryTree } from "@/components/categories/category-tree";
|
||||
import { CategoryViewToggle } from "@/components/categories/category-view-toggle";
|
||||
import { CarBrandLogo } from "@/components/ui/car-brand-logo";
|
||||
import { ApiError, api } from "@/lib/api-client";
|
||||
import { setChatwootVehicle } from "@/lib/chatwoot";
|
||||
import { clearChatwootVehicle, setChatwootVehicle } from "@/lib/chatwoot";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { capture } from "@/lib/posthog";
|
||||
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
|
||||
import { cleanModelName } from "@/lib/vehicle";
|
||||
import type { CategoryNode, Vehicle } from "@sase/shared";
|
||||
@@ -46,6 +47,7 @@ function VehicleDetailPage() {
|
||||
const changeViewMode = (mode: "grid" | "tree" | "columns") => {
|
||||
setViewMode(mode);
|
||||
setUserSetting("categoryViewMode", mode);
|
||||
capture("category_view_changed", { mode, vehicle_id: id });
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
@@ -97,6 +99,7 @@ function VehicleDetailPage() {
|
||||
year: vehicle.year,
|
||||
});
|
||||
}
|
||||
return () => clearChatwootVehicle();
|
||||
}, [vehicle, id]);
|
||||
|
||||
if (vehicleLoading) {
|
||||
@@ -211,7 +214,7 @@ function VehicleDetailPage() {
|
||||
<CarBrandLogo brandName={vehicle.brandName} size={32} className="shrink-0" />
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="break-words text-2xl font-bold leading-tight">
|
||||
<h2 className="break-words font-[family-name:var(--font-display)] text-2xl font-bold leading-tight tracking-tight">
|
||||
{vehicle?.brandName} {cleanModelName(vehicle?.model)}{" "}
|
||||
{vehicle?.year && `(${vehicle.year})`}
|
||||
</h2>
|
||||
@@ -252,15 +255,29 @@ function VehicleDetailPage() {
|
||||
{/* Search box keeps its own padding when the columns view zeroes out
|
||||
CardContent padding. */}
|
||||
<div className={viewMode === "columns" && !searchActive ? "p-6 pb-0" : undefined}>
|
||||
<CatalogSearch vehicleId={id} onActiveChange={setSearchActive} />
|
||||
<CatalogSearch
|
||||
vehicleId={id}
|
||||
onActiveChange={(active) => {
|
||||
setSearchActive(active);
|
||||
if (active) capture("catalog_search_opened", { vehicle_id: id });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!searchActive &&
|
||||
(categoriesLoading ? (
|
||||
<div className="space-y-2">
|
||||
{KEYS_8.map((__k) => (
|
||||
<Skeleton key={__k} className="h-8 w-full" />
|
||||
))}
|
||||
</div>
|
||||
viewMode === "grid" ? (
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
{KEYS_8.map((__k) => (
|
||||
<Skeleton key={__k} className="h-24 w-full rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{KEYS_8.map((__k) => (
|
||||
<Skeleton key={__k} className="h-8 w-full" />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
) : !categoryTree || categoryTree.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border bg-muted/30 px-6 py-10 text-center">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
|
||||
Reference in New Issue
Block a user