@bytebrand/fe-ui-core-autobahn 1.0.123 → 1.0.124
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 +19 -5
- package/cards/CarRow.tsx +20 -7
- package/cards/cardParts.tsx +55 -22
- package/cards/types.ts +10 -0
- package/compat/VehicleSmallCard/VehicleData/VechiclePriceItem/VechiclePriceItem.tsx +3 -6
- package/compat/VehicleSmallCard/VehicleData/VehicleInfo/VehicleInfo.tsx +2 -2
- package/compat/VehicleSmallCard/VehicleSmallCard.tsx +6 -4
- package/data/toAutobahnCar.ts +24 -0
- package/package.json +2 -2
- package/vdp/VdpModules.tsx +9 -3
package/cards/CarCard.tsx
CHANGED
|
@@ -51,8 +51,14 @@ 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).
|
|
55
|
-
|
|
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;
|
|
56
62
|
/** fired on first hover/touch of the media box — the SRP uses it to lazy-load the gallery. */
|
|
57
63
|
onMediaEnter?: () => void;
|
|
58
64
|
/**
|
|
@@ -113,8 +119,16 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
113
119
|
onCall,
|
|
114
120
|
onOpenRateAdjust,
|
|
115
121
|
onShowFinancingDetails,
|
|
122
|
+
onModeChange,
|
|
116
123
|
}) => {
|
|
117
|
-
|
|
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
|
+
};
|
|
118
132
|
const [photoIndex, setPhotoIndex] = useState(0);
|
|
119
133
|
const [viewerOpen, setViewerOpen] = useState(false);
|
|
120
134
|
const mediaRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -160,7 +174,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
160
174
|
return (
|
|
161
175
|
<article
|
|
162
176
|
className="mcard mcard--interactive cc-card"
|
|
163
|
-
onClick={() => onOpen(car)}
|
|
177
|
+
onClick={() => onOpen(car, mode)}
|
|
164
178
|
style={{ display: 'flex', flexDirection: 'column' }}
|
|
165
179
|
>
|
|
166
180
|
<div
|
|
@@ -275,7 +289,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
275
289
|
</div>
|
|
276
290
|
<SpecLine items={cardSpec(car)} oneLine />
|
|
277
291
|
<div className="cc-price" style={{ marginTop: 0, borderTop: '1px solid var(--md-outline-variant)' }}>
|
|
278
|
-
<PriceTabs car={car} mode={mode} onModeChange={
|
|
292
|
+
<PriceTabs car={car} mode={mode} onModeChange={changeMode} t={t} />
|
|
279
293
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginTop: 10 }}>
|
|
280
294
|
<PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
|
|
281
295
|
<PriceEvalCol car={car} t={t} />
|
package/cards/CarRow.tsx
CHANGED
|
@@ -52,8 +52,13 @@ 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).
|
|
56
|
-
|
|
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;
|
|
57
62
|
/** fired on first hover/touch of the media box — the SRP uses it to lazy-load the gallery. */
|
|
58
63
|
onMediaEnter?: () => void;
|
|
59
64
|
/**
|
|
@@ -92,8 +97,16 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
92
97
|
t,
|
|
93
98
|
adminAction,
|
|
94
99
|
onShowFinancingDetails,
|
|
100
|
+
onModeChange,
|
|
95
101
|
}) => {
|
|
96
|
-
|
|
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
|
+
};
|
|
97
110
|
const [photoIndex, setPhotoIndex] = useState(0);
|
|
98
111
|
const mediaRef = useRef<HTMLDivElement | null>(null);
|
|
99
112
|
// always call the LATEST onMediaEnter without re-arming the observer on every render (the
|
|
@@ -146,7 +159,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
146
159
|
return (
|
|
147
160
|
<article
|
|
148
161
|
className="mcard mcard--interactive listcard"
|
|
149
|
-
onClick={() => onOpen(car)}
|
|
162
|
+
onClick={() => onOpen(car, mode)}
|
|
150
163
|
style={{ background: 'var(--md-sc-lowest)' }}
|
|
151
164
|
>
|
|
152
165
|
<div className="listcard-media" ref={mediaRef} style={{ position: 'relative' }} onMouseEnter={onMediaEnter} onTouchStart={onMediaEnter}>
|
|
@@ -268,7 +281,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
268
281
|
the previously-unused PricePaths/PriceKauf pair. Both variants share the same
|
|
269
282
|
Preisbewertung meter + CTA, anchored to the bottom (margin-top:auto on .pp-purchase). */}
|
|
270
283
|
<div className="listcard-price">
|
|
271
|
-
<PriceTabs car={car} mode={mode} onModeChange={
|
|
284
|
+
<PriceTabs car={car} mode={mode} onModeChange={changeMode} t={t} className="show-mobile" />
|
|
272
285
|
<div className="pp-purchase show-mobile">
|
|
273
286
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
|
274
287
|
<PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
|
|
@@ -287,7 +300,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
287
300
|
iconRight="arrow-right"
|
|
288
301
|
onClick={(e) => {
|
|
289
302
|
e.stopPropagation();
|
|
290
|
-
onOpen(car);
|
|
303
|
+
onOpen(car, mode);
|
|
291
304
|
}}
|
|
292
305
|
>
|
|
293
306
|
{t('autobahn:card.cta.viewOffer')}
|
|
@@ -336,7 +349,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
336
349
|
iconRight="arrow-right"
|
|
337
350
|
onClick={(e) => {
|
|
338
351
|
e.stopPropagation();
|
|
339
|
-
onOpen(car);
|
|
352
|
+
onOpen(car, mode);
|
|
340
353
|
}}
|
|
341
354
|
>
|
|
342
355
|
{t('autobahn:card.cta.viewOffer')}
|
package/cards/cardParts.tsx
CHANGED
|
@@ -1103,6 +1103,11 @@ 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';
|
|
1106
1111
|
// Terms of the config the displayed rate was actually computed with (per-car:
|
|
1107
1112
|
// dealer rate + the user's saved localStorage adjustments) — default config
|
|
1108
1113
|
// terms only when the car doesn't carry its own.
|
|
@@ -1130,42 +1135,70 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
|
|
|
1130
1135
|
) : (
|
|
1131
1136
|
<span className="pp-details-link">{t('autobahn:card.price.details')}</span>
|
|
1132
1137
|
);
|
|
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
|
+
);
|
|
1133
1164
|
return (
|
|
1134
1165
|
<div className={'pricepaths' + (className ? ' ' + className : '')}>
|
|
1135
|
-
<div className="pp-eyebrow">
|
|
1136
|
-
|
|
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 ⓘ /
|
|
1137
1170
|
struck price / mtl." group beside the rate that .cc-price and mobile's tab switcher
|
|
1138
1171
|
already use, so the rate + unit never fight for room in one flat row (the earlier
|
|
1139
1172
|
hand-rolled version here put them side by side and wrapped for any 4+-digit rate). */}
|
|
1140
|
-
<PriceHero car={car} mode=
|
|
1141
|
-
|
|
1142
|
-
|
|
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} />
|
|
1173
|
+
<PriceHero car={car} mode={heroMode} t={t} alwaysShowUnit />
|
|
1174
|
+
{heroMode === 'leasing' ? (
|
|
1175
|
+
<>
|
|
1152
1176
|
{hasLeasTerms && (
|
|
1153
|
-
<div className="pp-
|
|
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. */}
|
|
1177
|
+
<div className="pp-hero-terms">
|
|
1159
1178
|
{t('autobahn:card.price.leaseTermsCompactLine1', {
|
|
1160
1179
|
firstInstallment: leasFirstInstallment,
|
|
1161
1180
|
mileage: leasMileageStr,
|
|
1162
1181
|
})}{' · '}
|
|
1163
|
-
<br />
|
|
1164
1182
|
{t('autobahn:card.price.leaseTermsCompactLine2', { months: leasMonths })}{' '}
|
|
1165
1183
|
· {detailsLink('leasing')}
|
|
1166
1184
|
</div>
|
|
1167
1185
|
)}
|
|
1168
|
-
|
|
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
|
+
</>
|
|
1169
1202
|
)}
|
|
1170
1203
|
</div>
|
|
1171
1204
|
);
|
package/cards/types.ts
CHANGED
|
@@ -57,6 +57,16 @@ 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';
|
|
60
70
|
/**
|
|
61
71
|
* financing terms the `monthly` rate was computed with — the per-car merged
|
|
62
72
|
* config (dealer rate + the user's saved localStorage adjustments, ADV-157).
|
|
@@ -39,10 +39,9 @@ export interface IVehiclePriceItemProps {
|
|
|
39
39
|
isMarge?: boolean;
|
|
40
40
|
}
|
|
41
41
|
class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
|
|
42
|
-
routeToActiveTab
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
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=`).
|
|
46
45
|
|
|
47
46
|
render(): React.ReactNode {
|
|
48
47
|
const {
|
|
@@ -103,7 +102,6 @@ class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
|
|
|
103
102
|
...routeObj,
|
|
104
103
|
target,
|
|
105
104
|
rel,
|
|
106
|
-
onClick: this.routeToActiveTab,
|
|
107
105
|
className: priceItemCategoryClassName,
|
|
108
106
|
href: url
|
|
109
107
|
})}
|
|
@@ -188,7 +186,6 @@ class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
|
|
|
188
186
|
...routeObj,
|
|
189
187
|
target,
|
|
190
188
|
rel,
|
|
191
|
-
onClick: this.routeToActiveTab,
|
|
192
189
|
className: styles.priceItemPrice,
|
|
193
190
|
href: url
|
|
194
191
|
})}
|
|
@@ -166,8 +166,8 @@ const VehicleInfo: React.FC<IVehicleInfoProps> = (props) => {
|
|
|
166
166
|
),
|
|
167
167
|
...routeObj,
|
|
168
168
|
className: wrapClasses,
|
|
169
|
-
// ADV-
|
|
170
|
-
|
|
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.
|
|
171
171
|
href: url,
|
|
172
172
|
target,
|
|
173
173
|
rel
|
|
@@ -319,8 +319,9 @@ const VehicleSmallCard: FunctionComponent<IVehicleSmallCardProps> = (props) => {
|
|
|
319
319
|
target,
|
|
320
320
|
rel,
|
|
321
321
|
className: `${styles.redirectClassLink} ${styles.gridAreaTitle}`,
|
|
322
|
-
// ADV-
|
|
323
|
-
|
|
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).
|
|
324
325
|
href: url
|
|
325
326
|
})}
|
|
326
327
|
|
|
@@ -373,8 +374,9 @@ const VehicleSmallCard: FunctionComponent<IVehicleSmallCardProps> = (props) => {
|
|
|
373
374
|
target,
|
|
374
375
|
rel,
|
|
375
376
|
className: `${styles.redirectClassLink} ${styles.gridAreaImage}`,
|
|
376
|
-
// ADV-
|
|
377
|
-
|
|
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).
|
|
378
380
|
href: url
|
|
379
381
|
})}
|
|
380
382
|
<VehicleInfo {...vehicleInfoProps} />
|
package/data/toAutobahnCar.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
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
|
+
|
|
26
28
|
import { getPriceRating, getPredictablePrice } from '../utils/priceRating';
|
|
27
29
|
import { formatTimestamp } from '../utils/DateUtils';
|
|
28
30
|
import { getPowerLabel } from '../utils/power';
|
|
@@ -427,6 +429,27 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
|
|
|
427
429
|
const leaseMonths = leasingActive ? num(price?.leasing?.paybackPeriod) : undefined;
|
|
428
430
|
const leaseFirstInstallment = leasingActive ? num(price?.leasing?.firstInstallment) : undefined;
|
|
429
431
|
|
|
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
|
+
|
|
430
453
|
// --- optional: list price / savings — ONLY from a real strike price ---
|
|
431
454
|
// transformDataForVehicles exposes oldMonthlyInstallment / highestPrice via
|
|
432
455
|
// the financing spread, and price.historyPriceDifference. There is NO reliable
|
|
@@ -494,6 +517,7 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
|
|
|
494
517
|
|
|
495
518
|
// price
|
|
496
519
|
monthly,
|
|
520
|
+
defaultMode,
|
|
497
521
|
...(finFirstInstallment != null ? { finFirstInstallment } : {}),
|
|
498
522
|
...(finMonths != null ? { finMonths } : {}),
|
|
499
523
|
price: purchase,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.124",
|
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bytebrand/car-schema-selectors": "^2.0.7",
|
|
13
|
-
"@bytebrand/fe-financing": "^5.14.
|
|
13
|
+
"@bytebrand/fe-financing": "^5.14.20",
|
|
14
14
|
"classnames": "2.2.6",
|
|
15
15
|
"clsx": "^2.1.1",
|
|
16
16
|
"d3-format": "1.4.5",
|
package/vdp/VdpModules.tsx
CHANGED
|
@@ -1424,12 +1424,18 @@ export interface VdpStickyBarProps {
|
|
|
1424
1424
|
show: boolean;
|
|
1425
1425
|
/** primary CTA click. */
|
|
1426
1426
|
onSecure?: () => void;
|
|
1427
|
+
/** ADV-181: monthly rate for the CURRENTLY selected payment product (fin/leasing differ) —
|
|
1428
|
+
* falls back to vehicle.monthly (the financing rate) when omitted, the pre-ADV-181 display. */
|
|
1429
|
+
monthly?: number;
|
|
1430
|
+
/** ADV-181: label of the selected product ("Finanzierung"/"Leasing") — replaces the generic
|
|
1431
|
+
* "ab" eyebrow so the sticky bar's label always matches the rate it shows. */
|
|
1432
|
+
productLabel?: string;
|
|
1427
1433
|
/** i18n function (drilled from the page). */
|
|
1428
1434
|
t: TranslationFunction;
|
|
1429
1435
|
}
|
|
1430
1436
|
|
|
1431
1437
|
/** Desktop sticky summary bar (AS24 pattern) — docks to top, driven by `show`. */
|
|
1432
|
-
export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehicle, image, show, onSecure, t }) => (
|
|
1438
|
+
export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehicle, image, show, onSecure, monthly, productLabel, t }) => (
|
|
1433
1439
|
<div className={'vdp-stickytop hide-mobile' + (show ? ' vdp-stickytop--on' : '')}>
|
|
1434
1440
|
<div className="container" style={{ display: 'flex', alignItems: 'center', gap: 18, padding: '10px 24px' }}>
|
|
1435
1441
|
<img
|
|
@@ -1455,9 +1461,9 @@ export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehic
|
|
|
1455
1461
|
</div>
|
|
1456
1462
|
<div style={{ textAlign: 'right', flex: 'none' }}>
|
|
1457
1463
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, justifyContent: 'flex-end' }}>
|
|
1458
|
-
<span className="md-label-sm">{t('autobahn:sticky.from')}</span>
|
|
1464
|
+
<span className="md-label-sm">{productLabel || t('autobahn:sticky.from')}</span>
|
|
1459
1465
|
<span className="num" style={{ fontWeight: 700, fontSize: 20, color: 'var(--md-primary)', letterSpacing: '-.4px' }}>
|
|
1460
|
-
{vehicle.monthly} €
|
|
1466
|
+
{monthly ?? vehicle.monthly} €
|
|
1461
1467
|
</span>
|
|
1462
1468
|
<span className="md-label-sm">{t('autobahn:sticky.perMonth')}</span>
|
|
1463
1469
|
</div>
|