dev #67
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The old 66-line history page was a raw list with `any[]` typing, no search, no sort, no delete, no dates, no i18n and no analytics — opening a vehicle detail was the only action available. Replaces it with a real management surface: - Search-by-text (brand, model, VIN, year, engine) — client-side over the paginated payload, Turkish-locale case-folding - Sort dropdown (newest, oldest, brand A→Z, year DESC) - Per-item Remove with a confirmation dialog; mutation does optimistic setQueryData so the card disappears immediately, then invalidates the search-page recent-history query - Relative-time "decoded {when}" using Intl.RelativeTimeFormat with the active locale; absolute timestamp lives in title/aria for power users - Plan-lock badge: when the user's plan no longer covers a brand in their history, that card surfaces a Lock chip linking to /subscription - Brand logo, VIN with copy-to-clipboard, focus-visible rings, Link.preload intent so opening a vehicle is instant - Empty state (no history) and no-results state (filter wiped everything) are now distinct, both with the right CTA - PostHog events: history_viewed, history_search_used, history_sort_changed, history_load_more, history_item_clicked, history_vin_copied, history_item_deleted — full funnel coverage from open to action - Full i18n via new `history.*` block in tr.json/en.json, plus the page now fetches paginated (PAGE_SIZE × 8 first chunk, "show more" client-side)Three drift-prone catalog surfaces (main /catalog, /catalog/$brandName, and the legacy CategoryViewToggle) each carried their own inline view-mode toggle with subtly different aria labels, sizing, icon paths and even a "Sutun" typo. New surfaces will inherit a single primitive. - ViewModeToggle: grid/tree/columns radio-group with i18n labels driven by `catalog.view.*` keys; fan-out by passing `groupLabelKey`. - useViewMode(key): centralises read/write of `brandViewMode`/`modelViewMode` in user-settings — page just owns its current value. - CatalogHeader: title + subtitle + optional breadcrumb trail + back button + trailing actions slot, so every catalog drill page renders the same shell. i18n: adds catalog.subtitle, catalog.view.{groupLabel,grid,list,columns}, catalog.{brandSearch,modelSearch}{Placeholder,NoMatch}, catalog.{hideLocked, showLocked,lockedSection,lockedSectionHint,inPlanSection,goToModels, selectBrandHint,columnsBrowseHint,sortLabel}, catalog.sort.{newest,oldest, alphabetical}. These primitives stand alone in this commit — the main /catalog and brand-detail pages migrate to them in 10B and 10C.Migrates the brand index off its inline view toggle to the shared ViewModeToggle/CatalogHeader primitives from 10A, then adds the toolbox the audit called out: - Brand search: client-side, Turkish-locale fold over brandName; visible X-clear button; the no-match panel exposes the original query - In-plan brands rendered above locked ones with two sub-section headers ("Planındaki markalar" / "Plan dışı markalar"); locked section gets the "upgrade to access" microcopy - Hide-locked toggle with aria-pressed (also persisted as a click event so we can size the cohort that prefers a clean view) - Searching collapses the section split into one ranked list — matches the "show me what I typed" mental model - Locked brand cards/rows are now real `<Link to="/dashboard/subscription">` buttons so the upgrade target is one tap; firing `catalog_locked_brand_upgrade_clicked` with brand_name + surface - `hover:-translate-y-0.5` (jittery on grid scroll) → `hover:scale-[1.02]` - ARIA tablist replaced by the role="group" + aria-pressed pattern from the shared ViewModeToggle — keyboard story actually works now - Drops manual `encodeURIComponent(brand.brandName)` in `params`; TanStack Router already encodes — caught the audit's double-encoding risk New PostHog events: catalog_brands_viewed, catalog_brand_search_used, catalog_hide_locked_toggled, catalog_view_mode_changed, catalog_brand_clicked, catalog_locked_brand_upgrade_clicked.Migrates the brand-detail page to the shared CatalogHeader (with real breadcrumb) + ViewModeToggle from 10A. Adds the toolbox the audit called out and quietly fixes the "Sutun" typo by retiring the inline buttons. Model browsing - Search across {model, engine, year, bodyType} with Turkish-locale fold - Sort dropdown: year DESC (default), year ASC, model A→Z — replaces the silent API-defined order - No-match state with the original query exposed - ModelCard attributes now Badge-pilled (year secondary, others outline) instead of flat space-separated text — "1998 1.6 FSI Hatchback Manuel" was a wall of words - preload="intent" on each model link so the categories drill page is warmed up on hover Multi-catalog selector - iconForService(): Pl24 → Layers, Pcat → Database, Emex → Zap, Tecdoc → Wrench, fallback BookOpen. Four identical BookOpen tiles read as one catalog before. - Drops the raw `serviceName` muted-text exposure under each tile Breadcrumb / header - "Katalog / Volkswagen / Pl24" now renders through CatalogHeader; the inline `<div className="text-xs">` tree got retired - Back button stays handle multi-catalog vs single-source distinction Loading - Removed the doubled-up `Loader2 "Modeller yükleniyor…"` line; skeleton alone is enough PostHog: catalog_models_viewed, catalog_model_search_used, catalog_model_sort_changed, catalog_subcatalog_selected, catalog_model_clicked, catalog_view_mode_changed (surface=models).Brings the secondary catalog entry points onto the shared header so breadcrumbs and back behaviour stop drifting from /catalog/:brandName. pcat/$catalogId - Switches the inline "ArrowLeft + h1" combo to CatalogHeader with a Katalog / {CATALOG_ID} breadcrumb - Replaces `Array.from({ length: 8 })` + numeric index keys with KEYS_10 (stable, no React-key warning risk) - `{model.carsCount} araç` was hard-coded TR — now reuses `catalog.modelCount2` so EN-locale users see "N model(s)" - Empty-state img placeholder swapped from arbitrary emerald to `bg-primary/10 text-primary` (token-driven, theme-correct) - preload="intent" on model links emex/$catalogCode - CatalogHeader with breadcrumb; reset button moves into the actions slot - Adds `catalog_emex_wizard_opened` view event for funnel parity with pl24/pcat — emex wizard is the highest-cognitive-load surface and we had zero data on its open-vs-abandon rate Future passes will translate the remaining hard-coded TR phrases inside the wizard body and the matched-vehicles list — kept out of this commit to avoid sprawl.Adds a horizontal scrollable "Son ziyaret ettiklerin" strip above the main brand grid. Renders only when the user has actually opened at least one brand detail page before — silent in cold-start state. Why localStorage, not a backend endpoint - This is a behavioural shortcut, not authoritative state. Adding a user_recent_brands table for data we don't have yet is premature. - Keying by `sase-recent-brands-${userId}` mirrors the trial-banner scoping pattern; a second user on the same browser doesn't inherit the first user's list. - localStorage failures (private mode, quota) silently degrade — the strip just stays hidden, never throws. How a brand gets added - Tracked at the destination (`/catalog/:brandName` visit), not on the link click. A click that never resolves into a real visit (auth gate, slow nav cancel) shouldn't be a "recently used" signal. - 12 entries stored, 8 surfaced. Headroom for future ranking (e.g. weight by frequency × recency) without re-recording history. Plan-lock awareness - The strip cross-references the `/catalog/brands` access map, so a brand the user opened while on Full and then lost on a downgrade shows the same lock chip + amber upgrade route used by the main grid. - New PostHog event: `catalog_recent_brand_clicked`. Locked recent chips reuse `catalog_locked_brand_upgrade_clicked` with `surface: "recents"` for funnel distinction. i18n: `catalog.recentSection` (TR: "Son ziyaret ettiklerin" / EN: "Recently visited").