@bytebrand/fe-ui-core-autobahn 1.0.85 → 1.0.87
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 +13 -12
- package/cards/CarRow.tsx +12 -11
- package/cards/types.ts +7 -0
- package/compat/CookieModal/modals/ManageCookieModal/ManageCookieModal.tsx +16 -11
- package/compat/CookieModal/modals/PreviewCookieModal/PreviewCookieModal.tsx +4 -3
- package/data/toAutobahnCar.ts +21 -1
- package/package.json +30 -30
- package/theme/md3.css +1 -1
- package/vdp/FinanceCalculator.tsx +6 -1
- package/vdp/VdpModules.tsx +3 -0
package/cards/CarCard.tsx
CHANGED
|
@@ -100,16 +100,17 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
100
100
|
const [mode, setMode] = useState<PaymentMode>('financing');
|
|
101
101
|
const [photoIndex, setPhotoIndex] = useState(0);
|
|
102
102
|
const [viewerOpen, setViewerOpen] = useState(false);
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// as-good-as-new gate (mileage < 1,000 km AND first registration unknown or < 12 months) —
|
|
104
|
+
// undefined (older AutobahnCar producer not yet updated) fails OPEN, keeping prior behaviour.
|
|
105
|
+
const asGoodAsNew = car.asGoodAsNew !== false;
|
|
106
|
+
const showDrop = asGoodAsNew && car.drop != null;
|
|
107
|
+
const showSave = asGoodAsNew && !showDrop && car.savings != null && car.savings > 600;
|
|
108
|
+
const showPct = asGoodAsNew && car.dropPct != null && car.dropPct > 0;
|
|
109
|
+
const showOfferBadge = !!(car.isNew || showDrop || showSave || showPct || car.zeroDown);
|
|
105
110
|
// PM call: the badge row only fits 3 pills — when Drop/Save + Percent + ZeroDown are all
|
|
106
111
|
// active that's already 3, so NEU is dropped rather than causing a 4th to overflow.
|
|
107
|
-
const activeDealChips = [
|
|
108
|
-
|
|
109
|
-
car.dropPct != null && car.dropPct > 0,
|
|
110
|
-
!!car.zeroDown,
|
|
111
|
-
].filter(Boolean).length;
|
|
112
|
-
const showNeu = car.isNew && activeDealChips < 3;
|
|
112
|
+
const activeDealChips = [showDrop || showSave, showPct, !!car.zeroDown].filter(Boolean).length;
|
|
113
|
+
const showNeu = asGoodAsNew && car.isNew && activeDealChips < 3;
|
|
113
114
|
return (
|
|
114
115
|
<article
|
|
115
116
|
className="mcard mcard--interactive cc-card"
|
|
@@ -134,7 +135,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
134
135
|
{/* top-left signal stack (heart is top-right): NEU badge + ONE deal signal + the
|
|
135
136
|
"⚡ Angebot" badge (own line, Figma) below it. DropChip takes precedence over
|
|
136
137
|
SaveChip; each only renders when its real data exists. */}
|
|
137
|
-
{(showOfferBadge ||
|
|
138
|
+
{(showOfferBadge || showPct) && (
|
|
138
139
|
<div
|
|
139
140
|
className="cc-badges"
|
|
140
141
|
style={{
|
|
@@ -150,8 +151,8 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
150
151
|
}}
|
|
151
152
|
>
|
|
152
153
|
<div style={{ display: 'flex', flexWrap: 'nowrap', gap: 5, alignItems: 'center' }}>
|
|
153
|
-
{
|
|
154
|
-
<PercentChip pct={car.dropPct} />
|
|
154
|
+
{showDrop ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
155
|
+
<PercentChip pct={showPct ? car.dropPct : undefined} />
|
|
155
156
|
<ZeroDownChip
|
|
156
157
|
t={t}
|
|
157
158
|
show={car.zeroDown}
|
|
@@ -205,7 +206,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
205
206
|
<div className="cc-price" style={{ marginTop: 4, borderTop: '1px solid var(--md-outline-variant)' }}>
|
|
206
207
|
<PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} />
|
|
207
208
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginTop: 10 }}>
|
|
208
|
-
<PriceHero car={car} mode={mode} t={t} />
|
|
209
|
+
<PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
|
|
209
210
|
<PriceEvalCol car={car} t={t} />
|
|
210
211
|
</div>
|
|
211
212
|
{/* same fallback CardCtaRow already uses below: pages that haven't wired a real
|
package/cards/CarRow.tsx
CHANGED
|
@@ -81,15 +81,16 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
81
81
|
}) => {
|
|
82
82
|
const [mode, setMode] = useState<PaymentMode>('financing');
|
|
83
83
|
const [photoIndex, setPhotoIndex] = useState(0);
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
// as-good-as-new gate (mileage < 1,000 km AND first registration unknown or < 12 months) —
|
|
85
|
+
// undefined (older AutobahnCar producer not yet updated) fails OPEN, keeping prior behaviour.
|
|
86
|
+
const asGoodAsNew = car.asGoodAsNew !== false;
|
|
87
|
+
const showDrop = asGoodAsNew && car.drop != null;
|
|
88
|
+
const showSave = asGoodAsNew && !showDrop && car.savings != null && car.savings > 600;
|
|
89
|
+
const showPct = asGoodAsNew && car.dropPct != null && car.dropPct > 0;
|
|
90
|
+
const showOfferBadge = !!(car.isNew || showDrop || showSave || showPct || car.zeroDown);
|
|
86
91
|
// same overflow rule as CarCard: the row only fits 3 pills, so NEU drops when the other 3 are all active.
|
|
87
|
-
const activeDealChips = [
|
|
88
|
-
|
|
89
|
-
car.dropPct != null && car.dropPct > 0,
|
|
90
|
-
!!car.zeroDown,
|
|
91
|
-
].filter(Boolean).length;
|
|
92
|
-
const showNeu = car.isNew && activeDealChips < 3;
|
|
92
|
+
const activeDealChips = [showDrop || showSave, showPct, !!car.zeroDown].filter(Boolean).length;
|
|
93
|
+
const showNeu = asGoodAsNew && car.isNew && activeDealChips < 3;
|
|
93
94
|
const facts: Array<[icon: string, value: string]> = [
|
|
94
95
|
['gauge', fmtKm(car.km)],
|
|
95
96
|
['calendar', t('autobahn:card.specs.firstRegistration', { reg: car.reg })],
|
|
@@ -130,8 +131,8 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
130
131
|
}}
|
|
131
132
|
>
|
|
132
133
|
<div style={{ display: 'flex', flexWrap: 'nowrap', alignItems: 'center', gap: 5 }}>
|
|
133
|
-
{
|
|
134
|
-
<PercentChip pct={car.dropPct} />
|
|
134
|
+
{showDrop ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
135
|
+
<PercentChip pct={showPct ? car.dropPct : undefined} />
|
|
135
136
|
<ZeroDownChip t={t} show={car.zeroDown} compact className="show-mobile" />
|
|
136
137
|
<ZeroDownChip
|
|
137
138
|
t={t}
|
|
@@ -245,7 +246,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
245
246
|
<PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} className="show-mobile" />
|
|
246
247
|
<div className="pp-purchase show-mobile">
|
|
247
248
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
|
248
|
-
<PriceHero car={car} mode={mode} t={t} />
|
|
249
|
+
<PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
|
|
249
250
|
{car.rating && (
|
|
250
251
|
<StatChip tone={ratingTone(car.rating)} icon={ratingTone(car.rating) === 'good' ? 'check' : 'minus'}>
|
|
251
252
|
{t('autobahn:tier.' + car.rating)}
|
package/cards/types.ts
CHANGED
|
@@ -118,6 +118,13 @@ export interface AutobahnCar {
|
|
|
118
118
|
dropPct?: number;
|
|
119
119
|
/** 0 € Anzahlung available (offer.showDownPayment) — drives the "0 € Anz." badge. */
|
|
120
120
|
zeroDown?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* as-good-as-new gate (mileage < 1,000 km AND first registration unknown or < 12 months) —
|
|
123
|
+
* product rule for the drop/percent/NEU deal signals AND the strike-price/UVP-Rate display
|
|
124
|
+
* (see toAutobahnCar.ts's isStrikeShown). Independent of NEU's own showNewLabel (which also
|
|
125
|
+
* requires a recent modificationDate). Does NOT gate zeroDown.
|
|
126
|
+
*/
|
|
127
|
+
asGoodAsNew?: boolean;
|
|
121
128
|
|
|
122
129
|
// --- NEEDS DATA: no real source yet (NEVER fabricate) ---
|
|
123
130
|
/** original list price (for the struck-through reference price). */
|
|
@@ -28,13 +28,20 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
28
28
|
|
|
29
29
|
useEffect(
|
|
30
30
|
() => {
|
|
31
|
+
// switches represent "is allowed" (true = allowed). Default to off (nothing
|
|
32
|
+
// allowed) unless the visitor already saved a choice on a previous visit.
|
|
33
|
+
const savedConfigRaw = localStorage.getItem('cookieConfig');
|
|
34
|
+
const savedConfig = savedConfigRaw ? JSON.parse(savedConfigRaw) : null;
|
|
35
|
+
const savedDisabledAnalytics: string[] = savedConfig?.analytics || [];
|
|
36
|
+
const savedDisabledMarketing: string[] = savedConfig?.marketing || [];
|
|
37
|
+
|
|
31
38
|
const newAnalyticsSwitches = Object.values(COOKIE_SCHEMA.analytics)
|
|
32
39
|
.reduce((acc, val) => acc.concat(val), [])
|
|
33
|
-
.reduce((acc, item) => ({ ...acc, [item]:
|
|
40
|
+
.reduce((acc, item) => ({ ...acc, [item]: !!savedConfig && !savedDisabledAnalytics.includes(item) }), {});
|
|
34
41
|
|
|
35
42
|
const newMarketingSwitches = Object.values(COOKIE_SCHEMA.marketing)
|
|
36
43
|
.reduce((acc, val) => acc.concat(val), [])
|
|
37
|
-
.reduce((acc, item) => ({ ...acc, [item]:
|
|
44
|
+
.reduce((acc, item) => ({ ...acc, [item]: !!savedConfig && !savedDisabledMarketing.includes(item) }), {});
|
|
38
45
|
setAnalyticsSwitches(newAnalyticsSwitches);
|
|
39
46
|
setMarketingSwitches(newMarketingSwitches);
|
|
40
47
|
},
|
|
@@ -42,11 +49,9 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
42
49
|
);
|
|
43
50
|
|
|
44
51
|
const onHandleAnalyticsSwitchValue = (value: boolean) => {
|
|
45
|
-
// per-item state is stored inverted (false = this item is on — see isChecked below),
|
|
46
|
-
// so turning the master ON (value=true) must store false on every item, not true.
|
|
47
52
|
const newAnalyticsSwitches = Object.values(COOKIE_SCHEMA.analytics)
|
|
48
53
|
.reduce((acc, val) => acc.concat(val), [])
|
|
49
|
-
.reduce((acc, item) => ({ ...acc, [item]:
|
|
54
|
+
.reduce((acc, item) => ({ ...acc, [item]: value }), {});
|
|
50
55
|
|
|
51
56
|
setAnalyticsSwitches(newAnalyticsSwitches);
|
|
52
57
|
setAnalyticsMasterSwitch(value);
|
|
@@ -55,7 +60,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
55
60
|
const onHandleMarketingSwitchValue = (value: boolean) => {
|
|
56
61
|
const newMarketingSwitches = Object.values(COOKIE_SCHEMA.marketing)
|
|
57
62
|
.reduce((acc, val) => acc.concat(val), [])
|
|
58
|
-
.reduce((acc, item) => ({ ...acc, [item]:
|
|
63
|
+
.reduce((acc, item) => ({ ...acc, [item]: value }), {});
|
|
59
64
|
setMarketingSwitches(newMarketingSwitches);
|
|
60
65
|
setMarketingMasterSwitch(value);
|
|
61
66
|
};
|
|
@@ -136,7 +141,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
136
141
|
{Object.keys(COOKIE_SCHEMA.analytics).map(cookie => {
|
|
137
142
|
const getItemsFromCategory = _get(COOKIE_SCHEMA, `analytics['${cookie}']`, null);
|
|
138
143
|
const getLinkFromCategory = _get(COOKIE_SCHEMA_LINKS, [cookie], '');
|
|
139
|
-
const isChecked = getItemsFromCategory.every((item: string) => analyticsSwitches[item] ===
|
|
144
|
+
const isChecked = getItemsFromCategory.every((item: string) => analyticsSwitches[item] === true);
|
|
140
145
|
return (
|
|
141
146
|
<div className={styles.switchBlock} key={cookie} >
|
|
142
147
|
<span className={styles.switchBlockTitle}>{cookie}</span>
|
|
@@ -144,7 +149,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
144
149
|
<MaterialSwitch
|
|
145
150
|
sx={{ marginLeft: 'auto' }}
|
|
146
151
|
value={isChecked}
|
|
147
|
-
onChange={() => onHandleChangeInnerSwitchAnalytics(getItemsFromCategory,
|
|
152
|
+
onChange={(newValue: boolean) => onHandleChangeInnerSwitchAnalytics(getItemsFromCategory, newValue)}
|
|
148
153
|
/>
|
|
149
154
|
</div>
|
|
150
155
|
);
|
|
@@ -160,7 +165,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
160
165
|
{Object.keys(COOKIE_SCHEMA.marketing).map(cookie => {
|
|
161
166
|
const getItemsFromCategory = _get(COOKIE_SCHEMA, `marketing['${cookie}']`, null);
|
|
162
167
|
const getLinkFromCategory = _get(COOKIE_SCHEMA_LINKS, [cookie], '');
|
|
163
|
-
const isChecked = getItemsFromCategory.every((item: string) => marketingSwitches[item] ===
|
|
168
|
+
const isChecked = getItemsFromCategory.every((item: string) => marketingSwitches[item] === true);
|
|
164
169
|
return (
|
|
165
170
|
<div className={styles.switchBlock} key={cookie} >
|
|
166
171
|
<span className={styles.switchBlockTitle}>{cookie}</span>
|
|
@@ -168,7 +173,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
168
173
|
<MaterialSwitch
|
|
169
174
|
sx={{ marginLeft: 'auto' }}
|
|
170
175
|
value={isChecked}
|
|
171
|
-
onChange={() => onHandleChangeInnerSwitchMarketing(getItemsFromCategory,
|
|
176
|
+
onChange={(newValue: boolean) => onHandleChangeInnerSwitchMarketing(getItemsFromCategory, newValue)}
|
|
172
177
|
/>
|
|
173
178
|
</div>
|
|
174
179
|
)
|
|
@@ -177,7 +182,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
177
182
|
</div>
|
|
178
183
|
<div className={styles.buttonsContainer}>
|
|
179
184
|
<Button onClick={() => onAccept()} className={classNames(styles.modalBtn, styles.outlinedBtn)} >Auswahl speichern</Button>
|
|
180
|
-
<Button onClick={() => onAccept({})} className={styles.modalBtn}>Alles akzeptieren</Button>
|
|
185
|
+
<Button onClick={() => onAccept({ marketing: [], analytics: [] })} className={styles.modalBtn}>Alles akzeptieren</Button>
|
|
181
186
|
</div>
|
|
182
187
|
</div>
|
|
183
188
|
);
|
|
@@ -3,6 +3,7 @@ import IconSVG from '../../../../iconsvg/IconSVG';
|
|
|
3
3
|
import Button from '../../../Button/Button';
|
|
4
4
|
import styles from './PreviewCookieModal.styl';
|
|
5
5
|
import { updateCookieList } from '../../../../utils/CommonUtils';
|
|
6
|
+
import { COOKIE_SCHEMA } from '../../../../constants/common';
|
|
6
7
|
|
|
7
8
|
interface IPreviewCookieModal {
|
|
8
9
|
toggleModal: () => void;
|
|
@@ -13,15 +14,15 @@ interface IPreviewCookieModal {
|
|
|
13
14
|
const PreviewCookieModal = ({ toggleModal, setModal, handleSentryInit } : IPreviewCookieModal) => {
|
|
14
15
|
const environment = window.location.origin;
|
|
15
16
|
const onAcceptAll = () => {
|
|
16
|
-
localStorage.setItem('cookieConfig', JSON.stringify({}));
|
|
17
|
+
localStorage.setItem('cookieConfig', JSON.stringify({ marketing: [], analytics: [] }));
|
|
17
18
|
updateCookieList(handleSentryInit);
|
|
18
19
|
toggleModal();
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
const onDisableAll = () => {
|
|
22
23
|
localStorage.setItem('cookieConfig', JSON.stringify({
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
marketing: Object.values(COOKIE_SCHEMA.marketing).reduce((acc: string[], val: string[]) => acc.concat(val), []),
|
|
25
|
+
analytics: Object.values(COOKIE_SCHEMA.analytics).reduce((acc: string[], val: string[]) => acc.concat(val), [])
|
|
25
26
|
}));
|
|
26
27
|
updateCookieList(handleSentryInit);
|
|
27
28
|
toggleModal();
|
package/data/toAutobahnCar.ts
CHANGED
|
@@ -96,6 +96,22 @@ function num(v: any): number | undefined {
|
|
|
96
96
|
return Number.isFinite(n) ? n : undefined;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// strike-price ("UVP-Rate"/list-price) eligibility — a separate product rule from the NEU
|
|
100
|
+
// badge's own showNewLabel (which also requires a recent modificationDate; see fe-main's
|
|
101
|
+
// DataTransformers.ts shouldShowNewLabel). Here: as-good-as-new only — mileage under 1,000 km
|
|
102
|
+
// AND first registration either unknown or under 12 months old. Mirrors the identical gate
|
|
103
|
+
// added to VehicleDetailedNextPage.tsx's getFinancing() (VDP "Dein Angebot" card).
|
|
104
|
+
const STRIKE_MAX_MILEAGE_KM = 1000;
|
|
105
|
+
const STRIKE_MAX_AGE_MONTHS = 12;
|
|
106
|
+
const STRIKE_AVG_MS_PER_MONTH = 1000 * 60 * 60 * 24 * 30.44;
|
|
107
|
+
|
|
108
|
+
/** unix-seconds first registration (info.regDate) — unknown can't disqualify what we can't check. */
|
|
109
|
+
function isStrikeAgeEligible(firstRegistration?: number | null): boolean {
|
|
110
|
+
if (firstRegistration == null || !Number.isFinite(firstRegistration) || firstRegistration <= 0) return true;
|
|
111
|
+
const ageMonths = (Date.now() - firstRegistration * 1000) / STRIKE_AVG_MS_PER_MONTH;
|
|
112
|
+
return ageMonths < STRIKE_MAX_AGE_MONTHS;
|
|
113
|
+
}
|
|
114
|
+
|
|
99
115
|
/**
|
|
100
116
|
* Minimal de-DE fallback for the handful of cbd:* selector codes the cards show
|
|
101
117
|
* (fuel + gearbox). Used only when no `t` is injected (e.g. in unit tests).
|
|
@@ -391,7 +407,10 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
|
|
|
391
407
|
// purchase-price strike on the search payload today, so we leave listPrice /
|
|
392
408
|
// savings UNDEFINED unless a real highestPrice strike exists above the sale
|
|
393
409
|
// price. See fieldMapping.ts — flagged for backend confirmation.
|
|
394
|
-
|
|
410
|
+
// asGoodAsNew — mileage (info.mileage, same source as `km` below) + regTs (info.regDate,
|
|
411
|
+
// same raw timestamp as `reg` above) gate: see isStrikeAgeEligible.
|
|
412
|
+
const asGoodAsNew = Number(num(info.mileage) ?? 0) < STRIKE_MAX_MILEAGE_KM && isStrikeAgeEligible(regTs);
|
|
413
|
+
const isStrikeShown = !!price?.financing?.isStrikeShown && asGoodAsNew;
|
|
395
414
|
const highest = num(price?.financing?.highestPrice) ?? num(price?.highestPrice);
|
|
396
415
|
let listPrice: number | undefined;
|
|
397
416
|
let savings: number | undefined;
|
|
@@ -492,6 +511,7 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
|
|
|
492
511
|
...(dropPct != null ? { dropPct } : {}),
|
|
493
512
|
...(zeroDown ? { zeroDown } : {}),
|
|
494
513
|
...(isNew ? { isNew } : {}),
|
|
514
|
+
asGoodAsNew,
|
|
495
515
|
...(availability ? { availability } : {}),
|
|
496
516
|
...(viewsCount != null ? { viewsCount } : {}),
|
|
497
517
|
...(favoritesCount != null ? { favoritesCount } : {}),
|
package/package.json
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
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.12",
|
|
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.87",
|
|
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.12",
|
|
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
|
+
}
|
package/theme/md3.css
CHANGED
|
@@ -4471,7 +4471,7 @@ line-height: 31px;
|
|
|
4471
4471
|
flex: none;
|
|
4472
4472
|
}
|
|
4473
4473
|
.md3 .cc-price .pp-hero-rate {
|
|
4474
|
-
align-items:
|
|
4474
|
+
align-items: flex-end;
|
|
4475
4475
|
flex-wrap: nowrap;
|
|
4476
4476
|
}
|
|
4477
4477
|
/* separator line between the financing block and the leasing row (legacy-design parity) */
|
|
@@ -757,7 +757,12 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
757
757
|
</s>
|
|
758
758
|
</span>
|
|
759
759
|
)}
|
|
760
|
-
|
|
760
|
+
{/* no struck old rate above it (strike hidden) — nudge "/mtl." down so it
|
|
761
|
+
sits level with the blue hero rate instead of centering high in the row. */}
|
|
762
|
+
<span
|
|
763
|
+
className="fin-rate-example-suffix"
|
|
764
|
+
style={oldMonthly == null ? { paddingTop: 20 } : undefined}
|
|
765
|
+
>
|
|
761
766
|
{t('autobahn:finance.perMonth')}
|
|
762
767
|
</span>
|
|
763
768
|
</span>
|
package/vdp/VdpModules.tsx
CHANGED
|
@@ -1655,6 +1655,8 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1655
1655
|
</s>
|
|
1656
1656
|
</span>
|
|
1657
1657
|
)}
|
|
1658
|
+
{/* no struck old rate above it (strike hidden) — nudge "/ mtl." down so it
|
|
1659
|
+
sits level with the blue hero rate instead of centering high in the row. */}
|
|
1658
1660
|
<span
|
|
1659
1661
|
style={{
|
|
1660
1662
|
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
@@ -1662,6 +1664,7 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1662
1664
|
fontSize: 18,
|
|
1663
1665
|
lineHeight: '21px',
|
|
1664
1666
|
color: '#5B6B7F',
|
|
1667
|
+
...(oldMonthly == null ? { paddingTop: 20 } : {}),
|
|
1665
1668
|
}}
|
|
1666
1669
|
>
|
|
1667
1670
|
{t('autobahn:bottom.perMonth').replace(/^\/\s*/, '/ ')}
|