@bytebrand/fe-ui-core-autobahn 1.0.164 → 1.0.165

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -797,7 +797,8 @@
797
797
  "serviceHistory": "Scheckheft",
798
798
  "warranty": "Garantie",
799
799
  "yes": "Ja",
800
- "no": "Nein"
800
+ "no": "Nein",
801
+ "notSpecified": "keine Angabe"
801
802
  },
802
803
  "common": {
803
804
  "needsData": "Daten folgt"
@@ -797,7 +797,8 @@
797
797
  "serviceHistory": "Full service history",
798
798
  "warranty": "Warranty",
799
799
  "yes": "Yes",
800
- "no": "No"
800
+ "no": "No",
801
+ "notSpecified": "not specified"
801
802
  },
802
803
  "common": {
803
804
  "needsData": "Data coming soon"
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.164",
4
- "description": "auto.de md3 (Autobahn) design system — UI primitives, cards, chrome, VDP modules, theme CSS and locales. Ships raw TypeScript source (like fe-ui-core before it); each consumer's bundler compiles it.",
5
- "main": "index.ts",
6
- "license": "UNLICENSED",
7
- "peerDependencies": {
8
- "react": "^18.0.0",
9
- "react-dom": "^18.0.0"
10
- },
11
- "dependencies": {
12
- "@bytebrand/car-schema-selectors": "^2.0.7",
13
- "@bytebrand/fe-financing": "^5.14.20",
14
- "classnames": "2.2.6",
15
- "clsx": "^2.1.1",
16
- "d3-format": "1.4.5",
17
- "js-cookie": "^2.2.0",
18
- "lodash": "^4.17.21",
19
- "lucide-react": "^0.288.0",
20
- "mobx-react": "^7.6.0",
21
- "moment": "^2.29.1",
22
- "qs": "^6.6.0",
23
- "react-device-detect": "^2.1.2",
24
- "react-grid-system": "^5.0.2",
25
- "react-lazyload": "^3.2.0",
26
- "react-scroll": "^1.9.3",
27
- "react-slick": "^0.29.0",
28
- "slick-carousel": "1.8.1"
29
- }
30
- }
1
+ {
2
+ "name": "@bytebrand/fe-ui-core-autobahn",
3
+ "version": "1.0.165",
4
+ "description": "auto.de md3 (Autobahn) design system — UI primitives, cards, chrome, VDP modules, theme CSS and locales. Ships raw TypeScript source (like fe-ui-core before it); each consumer's bundler compiles it.",
5
+ "main": "index.ts",
6
+ "license": "UNLICENSED",
7
+ "peerDependencies": {
8
+ "react": "^18.0.0",
9
+ "react-dom": "^18.0.0"
10
+ },
11
+ "dependencies": {
12
+ "@bytebrand/car-schema-selectors": "^2.0.7",
13
+ "@bytebrand/fe-financing": "^5.14.20",
14
+ "classnames": "2.2.6",
15
+ "clsx": "^2.1.1",
16
+ "d3-format": "1.4.5",
17
+ "js-cookie": "^2.2.0",
18
+ "lodash": "^4.17.21",
19
+ "lucide-react": "^0.288.0",
20
+ "mobx-react": "^7.6.0",
21
+ "moment": "^2.29.1",
22
+ "qs": "^6.6.0",
23
+ "react-device-detect": "^2.1.2",
24
+ "react-grid-system": "^5.0.2",
25
+ "react-lazyload": "^3.2.0",
26
+ "react-scroll": "^1.9.3",
27
+ "react-slick": "^0.29.0",
28
+ "slick-carousel": "1.8.1"
29
+ }
30
+ }
@@ -1,41 +1,41 @@
1
- // Autobahn (auto.de redesign) — shared "scroll a just-opened/just-focused field to the top of the
2
- // page" behaviour, used by Dropdown.tsx and by fe-main's DualRange/RateSlider (FilterContent.tsx)
3
- // text fields on /search/detailed. On mobile, opening a dropdown's menu or focusing a text field
4
- // brings the on-screen keyboard up and/or needs room for a popover below the field — if the field
5
- // is sitting mid-page, there may not be enough space left below it once the keyboard/menu takes
6
- // its share. Scrolling the field to the very top of the page first maximizes what's available.
7
- //
8
- // Extracted out of Dropdown.tsx so it can be shared verbatim with fe-main's non-Dropdown form
9
- // fields (DualRange/RateSlider's text inputs) — same problem, same fix, one place to keep them
10
- // from drifting apart. See Dropdown.tsx's own call site for the original reasoning this was
11
- // pulled from.
12
- export function scrollFieldIntoView(field: HTMLElement): void {
13
- if (typeof window === 'undefined') return;
14
- // Mobile-only: the on-screen-keyboard/popover space problem this fixes doesn't exist on
15
- // desktop, where there's no keyboard pushing the viewport around. Same breakpoint + matchMedia
16
- // pattern as Modal.tsx/CardCarousel.tsx elsewhere in this lib.
17
- if (window.matchMedia('(min-width: 601px)').matches) return;
18
- // Anchor on whichever is higher up: the field itself, or a short field label/caption sitting
19
- // directly above it (own previous sibling, or — the common "label above a row of controls"
20
- // shape, e.g. "Marke & Modell" above the Make/Model row on /search/detailed — the wrapping
21
- // row's previous sibling). Checked by height, not by class name, so this works for ANY
22
- // caller's label without this needing to know that call site's specific markup.
23
- const MAX_LABEL_HEIGHT = 30;
24
- const asLabel = (el: Element | null): HTMLElement | null => {
25
- if (!(el instanceof HTMLElement)) return null;
26
- const h = el.getBoundingClientRect().height;
27
- return h > 0 && h <= MAX_LABEL_HEIGHT ? el : null;
28
- };
29
- const anchor =
30
- asLabel(field.previousElementSibling) ||
31
- asLabel(field.parentElement && field.parentElement.previousElementSibling) ||
32
- field;
33
- // No sticky-header reservation here: every current caller (NewMainPage '/', FilterContent on
34
- // '/search*') sits on a page where TopBar's own autoHideOnScroll is on (App.tsx: isHomePage ||
35
- // isSearchPage) — the header slides itself out of the way the instant this scroll starts,
36
- // independently of this call, so reserving space for it here would double-count.
37
- const GAP = 8;
38
- const rect = anchor.getBoundingClientRect();
39
- const targetY = window.scrollY + rect.top - GAP;
40
- window.scrollTo({ top: Math.max(0, targetY), behavior: 'smooth' });
41
- }
1
+ // Autobahn (auto.de redesign) — shared "scroll a just-opened/just-focused field to the top of the
2
+ // page" behaviour, used by Dropdown.tsx and by fe-main's DualRange/RateSlider (FilterContent.tsx)
3
+ // text fields on /search/detailed. On mobile, opening a dropdown's menu or focusing a text field
4
+ // brings the on-screen keyboard up and/or needs room for a popover below the field — if the field
5
+ // is sitting mid-page, there may not be enough space left below it once the keyboard/menu takes
6
+ // its share. Scrolling the field to the very top of the page first maximizes what's available.
7
+ //
8
+ // Extracted out of Dropdown.tsx so it can be shared verbatim with fe-main's non-Dropdown form
9
+ // fields (DualRange/RateSlider's text inputs) — same problem, same fix, one place to keep them
10
+ // from drifting apart. See Dropdown.tsx's own call site for the original reasoning this was
11
+ // pulled from.
12
+ export function scrollFieldIntoView(field: HTMLElement): void {
13
+ if (typeof window === 'undefined') return;
14
+ // Mobile-only: the on-screen-keyboard/popover space problem this fixes doesn't exist on
15
+ // desktop, where there's no keyboard pushing the viewport around. Same breakpoint + matchMedia
16
+ // pattern as Modal.tsx/CardCarousel.tsx elsewhere in this lib.
17
+ if (window.matchMedia('(min-width: 601px)').matches) return;
18
+ // Anchor on whichever is higher up: the field itself, or a short field label/caption sitting
19
+ // directly above it (own previous sibling, or — the common "label above a row of controls"
20
+ // shape, e.g. "Marke & Modell" above the Make/Model row on /search/detailed — the wrapping
21
+ // row's previous sibling). Checked by height, not by class name, so this works for ANY
22
+ // caller's label without this needing to know that call site's specific markup.
23
+ const MAX_LABEL_HEIGHT = 30;
24
+ const asLabel = (el: Element | null): HTMLElement | null => {
25
+ if (!(el instanceof HTMLElement)) return null;
26
+ const h = el.getBoundingClientRect().height;
27
+ return h > 0 && h <= MAX_LABEL_HEIGHT ? el : null;
28
+ };
29
+ const anchor =
30
+ asLabel(field.previousElementSibling) ||
31
+ asLabel(field.parentElement && field.parentElement.previousElementSibling) ||
32
+ field;
33
+ // No sticky-header reservation here: every current caller (NewMainPage '/', FilterContent on
34
+ // '/search*') sits on a page where TopBar's own autoHideOnScroll is on (App.tsx: isHomePage ||
35
+ // isSearchPage) — the header slides itself out of the way the instant this scroll starts,
36
+ // independently of this call, so reserving space for it here would double-count.
37
+ const GAP = 8;
38
+ const rect = anchor.getBoundingClientRect();
39
+ const targetY = window.scrollY + rect.top - GAP;
40
+ window.scrollTo({ top: Math.max(0, targetY), behavior: 'smooth' });
41
+ }
package/utils/index.ts CHANGED
@@ -83,3 +83,5 @@ export type { IValues, IFilters, IQuery } from './query';
83
83
  // Decorated car props (renders React UI; lives in vehiclesProps/ with its
84
84
  // IconContainer + Co2Widget dependencies under compat/).
85
85
  export { default as getDecoratedProps } from '../vehiclesProps/decoratedProps';
86
+ export { setStrictConditionOwners, isStrictConditionOwner, strictAccidentStatus } from '../vehiclesProps/accidentDisclosure';
87
+ export type { AccidentStatus } from '../vehiclesProps/accidentDisclosure';
@@ -92,7 +92,7 @@ export const VdpHeaderSummary: React.FunctionComponent<VdpHeaderCardProps> = ({
92
92
  ['zap', extractPS(vehicle.power)],
93
93
  ['fuel', stripParen(vehicle.fuel)],
94
94
  ['cog', vehicle.gear],
95
- ['user', t('autobahn:header.owners', { owners: vehicle.owners, count: vehicle.owners })],
95
+ ...(vehicle.owners == null ? [] : ([['user', t('autobahn:header.owners', { owners: vehicle.owners, count: vehicle.owners })]] as const)),
96
96
  ];
97
97
  return (
98
98
  <>
@@ -142,7 +142,7 @@ export const VdpFactsGrid: React.FunctionComponent<VdpHeaderCardProps> = ({ vehi
142
142
  ['calendar', vehicle.reg],
143
143
  ['cog', vehicle.gear],
144
144
  ['fuel', stripParen(vehicle.fuel)],
145
- ['user', t('autobahn:header.owners', { owners: vehicle.owners, count: vehicle.owners })],
145
+ ...(vehicle.owners == null ? [] : ([['user', t('autobahn:header.owners', { owners: vehicle.owners, count: vehicle.owners })]] as const)),
146
146
  ...(vehicle.body ? ([['car-front', vehicle.body]] as const) : []),
147
147
  ];
148
148
  return (
@@ -216,7 +216,7 @@ export const VdpHeaderCondition: React.FunctionComponent<VdpHeaderCardProps> = (
216
216
  // only the Garantie tile has one, linking out to auto.de's warranty policy page.
217
217
  const tiles: ReadonlyArray<readonly [string, string, string, string?]> = [
218
218
  ['wrench', t('autobahn:header.huLabel'), condition.hu === 'Neu' ? t('autobahn:header.huNew') : t('autobahn:header.huUntil', { date: condition.hu })],
219
- ['shield-check', t('autobahn:header.accidentFree'), condition.unfallfrei ? t('autobahn:header.yes') : t('autobahn:header.no')],
219
+ ['shield-check', t('autobahn:header.accidentFree'), condition.unfallfrei == null ? t('autobahn:header.notSpecified') : condition.unfallfrei ? t('autobahn:header.yes') : t('autobahn:header.no')],
220
220
  ['clipboard-check', t('autobahn:header.serviceHistory'), condition.scheckheft ? t('autobahn:header.yes') : t('autobahn:header.no')],
221
221
  ['award', t('autobahn:header.warranty'), condition.garantie, 'https://www.auto.de/garantie/'],
222
222
  ];
package/vdp/types.ts CHANGED
@@ -111,8 +111,8 @@ export interface Co2CostsSummary {
111
111
  export interface VehicleCondition {
112
112
  /** TÜV/HU validity, e.g. '03/2027' — or 'Neu' for a new HU. */
113
113
  hu: string;
114
- /** accident-free flag (Unfallfrei). */
115
- unfallfrei: boolean;
114
+ /** accident-free flag (Unfallfrei); null = not specified by the source ("keine Angabe"). */
115
+ unfallfrei: boolean | null;
116
116
  /** full service history (Scheckheftgepflegt). */
117
117
  scheckheft: boolean;
118
118
  /** warranty label, e.g. '12 Monate inkl.'. */
@@ -191,8 +191,8 @@ export interface VehicleDetail {
191
191
  fuel: string;
192
192
  /** gearbox label, e.g. 'Automatik' / 'Schaltgetriebe'. */
193
193
  gear: string;
194
- /** number of previous owners (rendered as 'N. Hand'). */
195
- owners: number;
194
+ /** number of previous owners (rendered as 'N. Hand'); null = not known, the fact is not shown. */
195
+ owners: number | null;
196
196
  /** number of seats. */
197
197
  seats: number;
198
198
  /** number of doors. */
@@ -302,7 +302,7 @@ export function defaultVdpSpecs(v: VehicleDetail): VdpSpec[] {
302
302
  [['cog', 'Getriebe', v.gear], true],
303
303
  [['users', 'Sitze', `${v.seats} Sitze`], false],
304
304
  [['door-open', 'Türen', `${v.doors} Türen`], false],
305
- [['user', 'Halter', `${v.owners}. Hand`], true], // 0 Vorbesitzer is valid
305
+ ...(v.owners == null ? [] : [[['user', 'Halter', `${v.owners}. Hand`], true] as const]), // 0 Vorbesitzer is valid
306
306
  [['palette', 'Außenfarbe', v.color], true],
307
307
  [['car-front', 'Karosserie', v.body], true],
308
308
  [['droplet', 'Verbrauch komb.', v.cons], true], // Pkw-EnVKV — exempt from this rule
@@ -0,0 +1,25 @@
1
+ // FINN handover v0.6 (Daniel, 15.10.2026): for the owners set here, accident and owner facts follow
2
+ // only what the source states — mainData.accidentDamaged false → accident-free, true → accident,
3
+ // empty → not specified; an empty owner count is not shown as "0. Hand". Every other dealer keeps
4
+ // the old display (accident-free unless `damaged`). The app sets the owners once at start.
5
+ let strictOwners: ReadonlyArray<string> = [];
6
+
7
+ export function setStrictConditionOwners(ownerIds: ReadonlyArray<string>): void {
8
+ strictOwners = (ownerIds || []).filter(Boolean);
9
+ }
10
+
11
+ export function isStrictConditionOwner(car: any): boolean {
12
+ const ownerId = car?.metaData?.ownerId;
13
+ return !!ownerId && strictOwners.includes(ownerId);
14
+ }
15
+
16
+ export type AccidentStatus = 'accidentFree' | 'accident' | 'notSpecified';
17
+
18
+ // The strict status, or undefined when the car's owner keeps the old display.
19
+ export function strictAccidentStatus(car: any): AccidentStatus | undefined {
20
+ if (!isStrictConditionOwner(car)) return undefined;
21
+ const value = car?.mainData?.accidentDamaged;
22
+ if (value === true) return 'accident';
23
+ if (value === false) return 'accidentFree';
24
+ return 'notSpecified';
25
+ }
@@ -6,6 +6,7 @@ import { formatTimestamp } from '../utils/DateUtils';
6
6
  import { DELIVERY_PERIODS_EXTRA } from '../constants/core';
7
7
  import moment from 'moment';
8
8
  import { IDecoratedProp } from '../types/types';
9
+ import { strictAccidentStatus } from './accidentDisclosure';
9
10
 
10
11
  interface IDecoratedProps {
11
12
  [key: string]: IDecoratedProp;
@@ -519,6 +520,9 @@ const getDecoratedLightProps = (
519
520
  title: t('cbd:mainData_accidentDamaged'),
520
521
  icon: 'wrench',
521
522
  get value() {
523
+ const strict = strictAccidentStatus(car);
524
+ if (strict === 'notSpecified') return t('vehicleProps:value.na');
525
+ if (strict) return strict === 'accident' ? t('vehicleProps:value.damaged') : t('vehicleProps:value.accidentFree');
522
526
  return damaged ? t('vehicleProps:value.damaged') : t('vehicleProps:value.accidentFree');
523
527
  }
524
528
  },
@@ -9,6 +9,7 @@ import { EMISSION_STICKERS_ICONS, WP_EMISSION_STICKERS_ICONS } from '../constant
9
9
  import { DELIVERY_PERIODS_EXTRA } from '../constants';
10
10
  import { IDecoratedProp, ICar, IFinancingUtils } from '../types/types';
11
11
  import { getFormattedPrice } from '../utils/CommonUtils';
12
+ import { strictAccidentStatus } from './accidentDisclosure';
12
13
 
13
14
  const formatDeliveryPeriod = (t: (key: string, options?: object) => string, deliveryPeriod: string) => {
14
15
  const count: number = +DELIVERY_PERIODS_EXTRA.find((period: any) => period.value === deliveryPeriod).label;
@@ -195,6 +196,9 @@ const getDecoratedProps = (
195
196
  title: t('cbd:mainData_accidentDamaged'),
196
197
  icon: 'wrench',
197
198
  get value() {
199
+ const strict = strictAccidentStatus(car);
200
+ if (strict === 'notSpecified') return t('vehicleProps:value.na');
201
+ if (strict) return strict === 'accident' ? t('vehicleProps:value.damaged') : t('vehicleProps:value.accidentFree');
198
202
  return damaged ? t('vehicleProps:value.damaged') : t('vehicleProps:value.accidentFree');
199
203
  }
200
204
  },