@bytebrand/fe-ui-core-autobahn 1.0.134 → 1.0.136

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.
package/cards/CarCard.tsx CHANGED
@@ -51,14 +51,8 @@ export interface CarCardProps {
51
51
  isFavorite: boolean;
52
52
  /** toggle the favorite; the heart calls this (click is kept off the card). */
53
53
  onToggleFavorite: () => void;
54
- /** fired when the card is clicked (opens the detail page). The second argument is the payment
55
- * tab the card was showing at click time (ADV-181) — pages forward it to the VDP via the
56
- * `?mode=` query flag so the originating card and the CDP always agree on the product. */
57
- onOpen: (car: AutobahnCar, mode?: PaymentMode) => void;
58
- /** ADV-181 — fired when the user explicitly switches the card's payment tab. Pages use it to
59
- * persist the per-car product choice (FinancingStore.setSelectedProduct) + analytics. The
60
- * card still owns the visual tab state; this is a notification, not a controlled input. */
61
- onModeChange?: (mode: PaymentMode) => void;
54
+ /** fired when the card is clicked (opens the detail page). */
55
+ onOpen: (car: AutobahnCar) => void;
62
56
  /** fired on first hover/touch of the media box — the SRP uses it to lazy-load the gallery. */
63
57
  onMediaEnter?: () => void;
64
58
  /**
@@ -119,16 +113,8 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
119
113
  onCall,
120
114
  onOpenRateAdjust,
121
115
  onShowFinancingDetails,
122
- onModeChange,
123
116
  }) => {
124
- // ADV-181: start on the centrally-selected default product (cheaper valid rate of
125
- // financing vs leasing, or the user's saved per-car choice) — computed by toAutobahnCar,
126
- // so SSR/hydration agree. Absent (older producer) → financing, the pre-ADV-181 default.
127
- const [mode, setMode] = useState<PaymentMode>(car.defaultMode ?? 'financing');
128
- const changeMode = (m: PaymentMode) => {
129
- setMode(m);
130
- onModeChange && onModeChange(m);
131
- };
117
+ const [mode, setMode] = useState<PaymentMode>('financing');
132
118
  const [photoIndex, setPhotoIndex] = useState(0);
133
119
  const [viewerOpen, setViewerOpen] = useState(false);
134
120
  const mediaRef = useRef<HTMLDivElement | null>(null);
@@ -174,7 +160,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
174
160
  return (
175
161
  <article
176
162
  className="mcard mcard--interactive cc-card"
177
- onClick={() => onOpen(car, mode)}
163
+ onClick={() => onOpen(car)}
178
164
  style={{ display: 'flex', flexDirection: 'column' }}
179
165
  >
180
166
  <div
@@ -289,7 +275,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
289
275
  </div>
290
276
  <SpecLine items={cardSpec(car)} oneLine />
291
277
  <div className="cc-price" style={{ marginTop: 0, borderTop: '1px solid var(--md-outline-variant)' }}>
292
- <PriceTabs car={car} mode={mode} onModeChange={changeMode} t={t} />
278
+ <PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} />
293
279
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginTop: 10 }}>
294
280
  <PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
295
281
  <PriceEvalCol car={car} t={t} />
package/cards/CarRow.tsx CHANGED
@@ -52,13 +52,8 @@ export interface CarRowProps {
52
52
  isFavorite: boolean;
53
53
  /** toggle the favorite; the hearts call this (click is kept off the card). */
54
54
  onToggleFavorite: () => void;
55
- /** fired when the card (or the CTA) is clicked (opens the detail page). The second argument is
56
- * the payment tab/product the card was showing at click time (ADV-181) — pages forward it to
57
- * the VDP via `?mode=` so the originating card and the CDP always agree on the product. */
58
- onOpen: (car: AutobahnCar, mode?: PaymentMode) => void;
59
- /** ADV-181 — fired when the user explicitly switches the mobile payment tab. Pages use it to
60
- * persist the per-car product choice (FinancingStore.setSelectedProduct) + analytics. */
61
- onModeChange?: (mode: PaymentMode) => void;
55
+ /** fired when the card (or the CTA) is clicked (opens the detail page). */
56
+ onOpen: (car: AutobahnCar) => void;
62
57
  /** fired on first hover/touch of the media box — the SRP uses it to lazy-load the gallery. */
63
58
  onMediaEnter?: () => void;
64
59
  /**
@@ -97,16 +92,8 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
97
92
  t,
98
93
  adminAction,
99
94
  onShowFinancingDetails,
100
- onModeChange,
101
95
  }) => {
102
- // ADV-181: start on the centrally-selected default product (see CarCard's identical init).
103
- // Drives the mobile tab switcher AND the mode reported to onOpen; the desktop branch's
104
- // untabbed PricePaths reads car.defaultMode directly.
105
- const [mode, setMode] = useState<PaymentMode>(car.defaultMode ?? 'financing');
106
- const changeMode = (m: PaymentMode) => {
107
- setMode(m);
108
- onModeChange && onModeChange(m);
109
- };
96
+ const [mode, setMode] = useState<PaymentMode>('financing');
110
97
  const [photoIndex, setPhotoIndex] = useState(0);
111
98
  const mediaRef = useRef<HTMLDivElement | null>(null);
112
99
  // always call the LATEST onMediaEnter without re-arming the observer on every render (the
@@ -159,7 +146,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
159
146
  return (
160
147
  <article
161
148
  className="mcard mcard--interactive listcard"
162
- onClick={() => onOpen(car, mode)}
149
+ onClick={() => onOpen(car)}
163
150
  style={{ background: 'var(--md-sc-lowest)' }}
164
151
  >
165
152
  <div className="listcard-media" ref={mediaRef} style={{ position: 'relative' }} onMouseEnter={onMediaEnter} onTouchStart={onMediaEnter}>
@@ -281,7 +268,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
281
268
  the previously-unused PricePaths/PriceKauf pair. Both variants share the same
282
269
  Preisbewertung meter + CTA, anchored to the bottom (margin-top:auto on .pp-purchase). */}
283
270
  <div className="listcard-price">
284
- <PriceTabs car={car} mode={mode} onModeChange={changeMode} t={t} className="show-mobile" />
271
+ <PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} className="show-mobile" />
285
272
  <div className="pp-purchase show-mobile">
286
273
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
287
274
  <PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
@@ -300,7 +287,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
300
287
  iconRight="arrow-right"
301
288
  onClick={(e) => {
302
289
  e.stopPropagation();
303
- onOpen(car, mode);
290
+ onOpen(car);
304
291
  }}
305
292
  >
306
293
  {t('autobahn:card.cta.viewOffer')}
@@ -349,7 +336,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
349
336
  iconRight="arrow-right"
350
337
  onClick={(e) => {
351
338
  e.stopPropagation();
352
- onOpen(car, mode);
339
+ onOpen(car);
353
340
  }}
354
341
  >
355
342
  {t('autobahn:card.cta.viewOffer')}
@@ -1103,11 +1103,6 @@ const PpRow: React.FunctionComponent<PpRowProps> = ({ label, value, unit, old, f
1103
1103
  */
1104
1104
  export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, className, onShowDetails }) => {
1105
1105
  const hasLease = car.lease != null;
1106
- // ADV-181: the hero rate follows the centrally-selected default product (cheaper valid rate,
1107
- // computed in toAutobahnCar). Leasing leads only when it actually won AND is displayable;
1108
- // 'buy'/absent both fall back to the financing hero (pre-ADV-181 layout) — the purchase price
1109
- // keeps its own PriceKauf row below either way, per the desktop rate-first rule.
1110
- const heroMode: PaymentMode = car.defaultMode === 'leasing' && hasLease ? 'leasing' : 'financing';
1111
1106
  // Terms of the config the displayed rate was actually computed with (per-car:
1112
1107
  // dealer rate + the user's saved localStorage adjustments) — default config
1113
1108
  // terms only when the car doesn't carry its own.
@@ -1135,70 +1130,42 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
1135
1130
  ) : (
1136
1131
  <span className="pp-details-link">{t('autobahn:card.price.details')}</span>
1137
1132
  );
1138
- const finTermsLine = (
1139
- <>
1140
- {t('autobahn:card.price.financeTerms', {
1141
- firstInstallment: finFirstInstallment,
1142
- months: finMonths,
1143
- count: finMonths,
1144
- })}{' '}
1145
- · {detailsLink('financing')}
1146
- </>
1147
- );
1148
- const leasCompactTerms = hasLeasTerms && (
1149
- <div className="pp-sub-terms">
1150
- {/* dedicated keys (not a rework of leaseTerms) so mobile/VDP's own reviewed
1151
- wording there stays untouched, but includes the same Sonderzahlung figure.
1152
- Split across two lines with an explicit <br/> (column is too narrow for all
1153
- three figures + Details on one line) — forced right after km/Jahr so "36" and
1154
- "Mon." always land on the same line instead of an awkward mid-number wrap. */}
1155
- {t('autobahn:card.price.leaseTermsCompactLine1', {
1156
- firstInstallment: leasFirstInstallment,
1157
- mileage: leasMileageStr,
1158
- })}{' · '}
1159
- <br />
1160
- {t('autobahn:card.price.leaseTermsCompactLine2', { months: leasMonths })}{' '}
1161
- · {detailsLink('leasing')}
1162
- </div>
1163
- );
1164
1133
  return (
1165
1134
  <div className={'pricepaths' + (className ? ' ' + className : '')}>
1166
- <div className="pp-eyebrow">
1167
- {t(heroMode === 'leasing' ? 'autobahn:card.price.leasingFrom' : 'autobahn:card.price.financingFrom')}
1168
- </div>
1169
- {/* reuses PriceHero's own rendering — the exact same stacked "UVP-Rate ⓘ /
1135
+ <div className="pp-eyebrow">{t('autobahn:card.price.financingFrom')}</div>
1136
+ {/* reuses PriceHero's own financing rendering — the exact same stacked "UVP-Rate ⓘ /
1170
1137
  struck price / mtl." group beside the rate that .cc-price and mobile's tab switcher
1171
1138
  already use, so the rate + unit never fight for room in one flat row (the earlier
1172
1139
  hand-rolled version here put them side by side and wrapped for any 4+-digit rate). */}
1173
- <PriceHero car={car} mode={heroMode} t={t} alwaysShowUnit />
1174
- {heroMode === 'leasing' ? (
1175
- <>
1140
+ <PriceHero car={car} mode="financing" t={t} alwaysShowUnit />
1141
+ <div className="pp-hero-terms">
1142
+ {t('autobahn:card.price.financeTerms', {
1143
+ firstInstallment: finFirstInstallment,
1144
+ months: finMonths,
1145
+ count: finMonths,
1146
+ })}{' '}
1147
+ · {detailsLink('financing')}
1148
+ </div>
1149
+ {hasLease && (
1150
+ <div className="pp-lease-row">
1151
+ <PpRow label={t('autobahn:card.price.leasing')} value={car.lease as number} unit="mtl." t={t} />
1176
1152
  {hasLeasTerms && (
1177
- <div className="pp-hero-terms">
1153
+ <div className="pp-sub-terms">
1154
+ {/* dedicated keys (not a rework of leaseTerms) so mobile/VDP's own reviewed
1155
+ wording there stays untouched, but includes the same Sonderzahlung figure.
1156
+ Split across two lines with an explicit <br/> (column is too narrow for all
1157
+ three figures + Details on one line) — forced right after km/Jahr so "36" and
1158
+ "Mon." always land on the same line instead of an awkward mid-number wrap. */}
1178
1159
  {t('autobahn:card.price.leaseTermsCompactLine1', {
1179
1160
  firstInstallment: leasFirstInstallment,
1180
1161
  mileage: leasMileageStr,
1181
1162
  })}{' · '}
1163
+ <br />
1182
1164
  {t('autobahn:card.price.leaseTermsCompactLine2', { months: leasMonths })}{' '}
1183
1165
  · {detailsLink('leasing')}
1184
1166
  </div>
1185
1167
  )}
1186
- {/* financing follows as the quiet row — same layout slot the leasing row used before. */}
1187
- <div className="pp-lease-row">
1188
- <PpRow label={t('autobahn:finance.mode.financing')} value={car.monthly} unit="mtl." t={t} />
1189
- <div className="pp-sub-terms">{finTermsLine}</div>
1190
- </div>
1191
- </>
1192
- ) : (
1193
- <>
1194
- <div className="pp-hero-terms">{finTermsLine}</div>
1195
- {hasLease && (
1196
- <div className="pp-lease-row">
1197
- <PpRow label={t('autobahn:card.price.leasing')} value={car.lease as number} unit="mtl." t={t} />
1198
- {leasCompactTerms}
1199
- </div>
1200
- )}
1201
- </>
1168
+ </div>
1202
1169
  )}
1203
1170
  </div>
1204
1171
  );
package/cards/types.ts CHANGED
@@ -57,16 +57,6 @@ export interface AutobahnCar {
57
57
  // --- price (always real) ---
58
58
  /** financing monthly rate in EUR — the LEAD price on every card. */
59
59
  monthly: number;
60
- /**
61
- * ADV-181 — which payment product leads on this card. Computed centrally by
62
- * toAutobahnCar via fe-financing's pickDefaultProduct (cheaper valid rate of
63
- * financing vs leasing; tie → financing; neither valid → 'buy'), then
64
- * overridden by the user's saved explicit per-car selection when the fe-main
65
- * transform stamped one (storeCar.selectedProduct). Cards initialise their
66
- * tab state from this; absent (older producer) → 'financing', the pre-ADV-181
67
- * behavior. Derived purely from card data, so SSR and hydration agree.
68
- */
69
- defaultMode?: 'financing' | 'leasing' | 'buy';
70
60
  /**
71
61
  * financing terms the `monthly` rate was computed with — the per-car merged
72
62
  * config (dealer rate + the user's saved localStorage adjustments, ADV-157).
@@ -39,9 +39,10 @@ export interface IVehiclePriceItemProps {
39
39
  isMarge?: boolean;
40
40
  }
41
41
  class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
42
- // ADV-181: the old routeToActiveTab (localStorage 'activeTabCDP' write) was removed — its only
43
- // reader was the unrouted legacy VDP sidebar, so the write was a no-op on the live site. The
44
- // live VDP resolves its tab centrally (lowest-rate rule / saved per-car choice / `?mode=`).
42
+ routeToActiveTab = () => {
43
+ const { priceTitle } = this.props;
44
+ localStorage.setItem('activeTabCDP', priceTitle);
45
+ }
45
46
 
46
47
  render(): React.ReactNode {
47
48
  const {
@@ -102,6 +103,7 @@ class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
102
103
  ...routeObj,
103
104
  target,
104
105
  rel,
106
+ onClick: this.routeToActiveTab,
105
107
  className: priceItemCategoryClassName,
106
108
  href: url
107
109
  })}
@@ -186,6 +188,7 @@ class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
186
188
  ...routeObj,
187
189
  target,
188
190
  rel,
191
+ onClick: this.routeToActiveTab,
189
192
  className: styles.priceItemPrice,
190
193
  href: url
191
194
  })}
@@ -166,8 +166,8 @@ const VehicleInfo: React.FC<IVehicleInfoProps> = (props) => {
166
166
  ),
167
167
  ...routeObj,
168
168
  className: wrapClasses,
169
- // ADV-181: the dead activeTabCDP write removed (its only reader was the unrouted legacy
170
- // VDP sidebar) — the live VDP resolves its tab centrally.
169
+ // ADV-116: opening a CDP from a card defaults to the Financing tab.
170
+ onClick: () => localStorage.setItem('activeTabCDP', t('vehicleProps:title.financing')),
171
171
  href: url,
172
172
  target,
173
173
  rel
@@ -319,9 +319,8 @@ const VehicleSmallCard: FunctionComponent<IVehicleSmallCardProps> = (props) => {
319
319
  target,
320
320
  rel,
321
321
  className: `${styles.redirectClassLink} ${styles.gridAreaTitle}`,
322
- // ADV-181: the dead activeTabCDP write removed (its only reader was the unrouted
323
- // legacy VDP sidebar) — the live VDP resolves its tab centrally (lowest-rate rule /
324
- // saved per-car choice / `?mode=` query flag).
322
+ // ADV-116: opening a CDP from a card defaults to the Financing tab.
323
+ onClick: () => localStorage.setItem('activeTabCDP', t('vehicleProps:title.financing')),
325
324
  href: url
326
325
  })}
327
326
 
@@ -374,9 +373,8 @@ const VehicleSmallCard: FunctionComponent<IVehicleSmallCardProps> = (props) => {
374
373
  target,
375
374
  rel,
376
375
  className: `${styles.redirectClassLink} ${styles.gridAreaImage}`,
377
- // ADV-181: the dead activeTabCDP write removed (its only reader was the unrouted
378
- // legacy VDP sidebar) — the live VDP resolves its tab centrally (lowest-rate rule /
379
- // saved per-car choice / `?mode=` query flag).
376
+ // ADV-116: opening a CDP from a card defaults to the Financing tab.
377
+ onClick: () => localStorage.setItem('activeTabCDP', t('vehicleProps:title.financing')),
380
378
  href: url
381
379
  })}
382
380
  <VehicleInfo {...vehicleInfoProps} />
@@ -23,8 +23,6 @@
23
23
  // Every field path below was confirmed against the real code (file:line in
24
24
  // ./fieldMapping.ts), not guessed.
25
25
 
26
- import { pickDefaultProduct } from '@bytebrand/fe-financing';
27
-
28
26
  import { getPriceRating, getPredictablePrice } from '../utils/priceRating';
29
27
  import { formatTimestamp } from '../utils/DateUtils';
30
28
  import { getPowerLabel } from '../utils/power';
@@ -429,27 +427,6 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
429
427
  const leaseMonths = leasingActive ? num(price?.leasing?.paybackPeriod) : undefined;
430
428
  const leaseFirstInstallment = leasingActive ? num(price?.leasing?.firstInstallment) : undefined;
431
429
 
432
- // --- ADV-181: leading payment product — ONE central rule for every card surface. ---
433
- // Automatic default = fe-financing's pickDefaultProduct on the same displayed rates the
434
- // card renders (cheaper valid rate wins, tie → financing, neither valid → 'buy'). The
435
- // user's saved explicit per-car choice (storeCar.selectedProduct, stamped by fe-main's
436
- // transformDataForVehicles from FinancingStore) overrides the automatic rule — but only
437
- // while that product is actually displayable on this card, so a stale "leasing" choice on
438
- // a car that aged out of leasing eligibility falls back to the automatic rule instead of
439
- // selecting a product the card can't show. Pure input → pure output: SSR (fe-landing)
440
- // computes the identical value from the same payload, so hydration never flips the tab.
441
- const picked = pickDefaultProduct({
442
- financing: { monthlyInstallment: monthly },
443
- leasing: { isActive: leasingActive, monthlyInstallment: lease ?? null },
444
- });
445
- const savedProduct = (storeCar as any).selectedProduct;
446
- const defaultMode: 'financing' | 'leasing' | 'buy' =
447
- savedProduct === 'leasing' && lease != null
448
- ? 'leasing'
449
- : savedProduct === 'financing' && monthly > 0
450
- ? 'financing'
451
- : picked.product;
452
-
453
430
  // --- optional: list price / savings — ONLY from a real strike price ---
454
431
  // transformDataForVehicles exposes oldMonthlyInstallment / highestPrice via
455
432
  // the financing spread, and price.historyPriceDifference. There is NO reliable
@@ -517,7 +494,6 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
517
494
 
518
495
  // price
519
496
  monthly,
520
- defaultMode,
521
497
  ...(finFirstInstallment != null ? { finFirstInstallment } : {}),
522
498
  ...(finMonths != null ? { finMonths } : {}),
523
499
  price: purchase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.134",
3
+ "version": "1.0.136",
4
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
5
  "main": "index.ts",
6
6
  "license": "UNLICENSED",
package/ui/FormField.tsx CHANGED
@@ -21,7 +21,12 @@ export interface FormFieldProps {
21
21
  /** override the field's own type (e.g. force 'password'). */
22
22
  type?: string;
23
23
  id?: string;
24
- autoComplete?: 'on' | 'off';
24
+ /** Any WHATWG autofill-detail token ('given-name', 'family-name', 'email', 'tel', …), not
25
+ * just 'on'/'off' — narrower typing here previously blocked callers from disambiguating
26
+ * structurally-similar fields (e.g. firstName/lastName) for the browser's own autofill,
27
+ * which can otherwise fill the same value into both (confirmed live: Chrome's
28
+ * profile-autofill on RegistrationForm.tsx's firstName/lastName, corrupting Odoo leads). */
29
+ autoComplete?: string;
25
30
  disabled?: boolean;
26
31
  onKeyPress?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
27
32
  /** suppress the built-in label (for layouts that supply their own external label next to the field). */
@@ -1606,18 +1606,12 @@ export interface VdpStickyBarProps {
1606
1606
  show: boolean;
1607
1607
  /** primary CTA click. */
1608
1608
  onSecure?: () => void;
1609
- /** ADV-181: monthly rate for the CURRENTLY selected payment product (fin/leasing differ) —
1610
- * falls back to vehicle.monthly (the financing rate) when omitted, the pre-ADV-181 display. */
1611
- monthly?: number;
1612
- /** ADV-181: label of the selected product ("Finanzierung"/"Leasing") — replaces the generic
1613
- * "ab" eyebrow so the sticky bar's label always matches the rate it shows. */
1614
- productLabel?: string;
1615
1609
  /** i18n function (drilled from the page). */
1616
1610
  t: TranslationFunction;
1617
1611
  }
1618
1612
 
1619
1613
  /** Desktop sticky summary bar (AS24 pattern) — docks to top, driven by `show`. */
1620
- export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehicle, image, show, onSecure, monthly, productLabel, t }) => (
1614
+ export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehicle, image, show, onSecure, t }) => (
1621
1615
  <div className={'vdp-stickytop hide-mobile' + (show ? ' vdp-stickytop--on' : '')}>
1622
1616
  <div className="container" style={{ display: 'flex', alignItems: 'center', gap: 18, padding: '10px 24px' }}>
1623
1617
  <img
@@ -1643,9 +1637,9 @@ export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehic
1643
1637
  </div>
1644
1638
  <div style={{ textAlign: 'right', flex: 'none' }}>
1645
1639
  <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, justifyContent: 'flex-end' }}>
1646
- <span className="md-label-sm">{productLabel || t('autobahn:sticky.from')}</span>
1640
+ <span className="md-label-sm">{t('autobahn:sticky.from')}</span>
1647
1641
  <span className="num" style={{ fontWeight: 700, fontSize: 20, color: 'var(--md-primary)', letterSpacing: '-.4px' }}>
1648
- {monthly ?? vehicle.monthly} €
1642
+ {vehicle.monthly} €
1649
1643
  </span>
1650
1644
  <span className="md-label-sm">{t('autobahn:sticky.perMonth')}</span>
1651
1645
  </div>