@bytebrand/fe-ui-core-autobahn 1.0.75 → 1.0.77

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
@@ -22,6 +22,7 @@ import { CardCarousel, IconButton, SaveChip, Button } from '../ui';
22
22
  import {
23
23
  SpecLine,
24
24
  EcoLine,
25
+ SeoLine,
25
26
  DropChip,
26
27
  PercentChip,
27
28
  ZeroDownChip,
@@ -232,6 +233,9 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
232
233
  onOpenRateAdjust={onOpenRateAdjust || (() => onOpen(car))}
233
234
  t={t}
234
235
  />
236
+ {/* legacy VehicleSmallCard's "SeoText" footer line (full spec + equipment list) —
237
+ renders nothing when the caller hasn't populated car.seoText. */}
238
+ <SeoLine car={car} style={{ marginTop: 6 }} />
235
239
  {adminAction && (
236
240
  <div style={{ marginTop: 8 }}>
237
241
  <Button
@@ -352,6 +352,35 @@ export const EcoLine: React.FunctionComponent<EcoLineProps> = ({ car, style, t }
352
352
  );
353
353
  };
354
354
 
355
+ export interface SeoLineProps {
356
+ car: AutobahnCar;
357
+ style?: React.CSSProperties;
358
+ }
359
+
360
+ /**
361
+ * Full spec + equipment footer line (fuel, category, gearbox, colour, condition, drive
362
+ * type, then every equipment highlight) — the small gray text under the card's CTA row,
363
+ * same field/format as the legacy VehicleSmallCard's SeoText. Renders nothing without
364
+ * `car.seoText` (needs-data: only fe-landing currently populates it).
365
+ */
366
+ export const SeoLine: React.FunctionComponent<SeoLineProps> = ({ car, style }) => {
367
+ if (!car.seoText) return null;
368
+ return (
369
+ <div
370
+ style={{
371
+ fontSize: 10,
372
+ lineHeight: '14px',
373
+ color: 'var(--md-on-surface-variant)',
374
+ maxHeight: 42,
375
+ overflow: 'hidden',
376
+ ...style,
377
+ }}
378
+ >
379
+ {car.seoText}
380
+ </div>
381
+ );
382
+ };
383
+
355
384
  export interface PricePathsProps {
356
385
  car: AutobahnCar;
357
386
  t: TranslationFunction;
package/cards/index.ts CHANGED
@@ -18,6 +18,7 @@ export {
18
18
  PriceBlock,
19
19
  DropChip,
20
20
  EcoLine,
21
+ SeoLine,
21
22
  PricePaths,
22
23
  PriceKauf,
23
24
  PriceMeter,
@@ -40,6 +41,7 @@ export type {
40
41
  PriceBlockProps,
41
42
  DropChipProps,
42
43
  EcoLineProps,
44
+ SeoLineProps,
43
45
  PricePathsProps,
44
46
  PpActivityProps,
45
47
  GalleryBadgesProps,
package/cards/types.ts CHANGED
@@ -92,6 +92,12 @@ export interface AutobahnCar {
92
92
  // --- trust / features (always real) ---
93
93
  /** equipment highlight chips (first few are shown on the row card). */
94
94
  features: string[];
95
+ /** full comma-joined spec + equipment line (fuel, category, gearbox, color, condition,
96
+ * drive type, then every highlight) — the small gray footer line under the card's CTA
97
+ * row, same field/format as the legacy VehicleSmallCard's SeoText. Optional: only
98
+ * callers that already build this string (fe-landing's transformSearchResultItem)
99
+ * populate it; others simply don't render the line. */
100
+ seoText?: string;
95
101
  /** price rating tone: 'fair' → warn pill, anything else → good pill. */
96
102
  rating: string;
97
103
  /** price rating label shown in the pill, e.g. 'Guter Preis'. */
package/constants.ts CHANGED
@@ -1,31 +1,31 @@
1
- // Autobahn (auto.de redesign) — shared lookup constants barrel.
2
- // Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
3
- // see that file's header) plus the legacy fe-ui-core shim re-exports below.
4
- export * from './constants/autobahn';
5
-
6
- // ---------------------------------------------------------------------------
7
- // Legacy shared constants (moved here from fe-ui-core).
8
- //
9
- // This block mirrors the public symbol set of the old
10
- // `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
11
- // specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
12
- // implementations live under ./constants/ (core.ts, app.ts, price.ts,
13
- // Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
14
- // ---------------------------------------------------------------------------
15
-
16
- export {
17
- offers,
18
- checkoutOffers,
19
- checkoutWarranties,
20
- checkoutSteps,
21
- checkoutSubStep,
22
- DELIVERY_PERIODS_EXTRA,
23
- THREE,
24
- WLTP_ENERGY_EFFICIENCY_CLASS
25
- } from './constants/core';
26
-
27
- export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
28
-
29
- export { priceRatings } from './constants/price';
30
-
31
- export { THROTTLE_DELAY } from './constants/Search';
1
+ // Autobahn (auto.de redesign) — shared lookup constants barrel.
2
+ // Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
3
+ // see that file's header) plus the legacy fe-ui-core shim re-exports below.
4
+ export * from './constants/autobahn';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Legacy shared constants (moved here from fe-ui-core).
8
+ //
9
+ // This block mirrors the public symbol set of the old
10
+ // `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
11
+ // specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
12
+ // implementations live under ./constants/ (core.ts, app.ts, price.ts,
13
+ // Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export {
17
+ offers,
18
+ checkoutOffers,
19
+ checkoutWarranties,
20
+ checkoutSteps,
21
+ checkoutSubStep,
22
+ DELIVERY_PERIODS_EXTRA,
23
+ THREE,
24
+ WLTP_ENERGY_EFFICIENCY_CLASS
25
+ } from './constants/core';
26
+
27
+ export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
28
+
29
+ export { priceRatings } from './constants/price';
30
+
31
+ export { THROTTLE_DELAY } from './constants/Search';
@@ -476,6 +476,10 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
476
476
  ratingLabel,
477
477
 
478
478
  // optional (only when real)
479
+ // full spec+equipment footer line — pass through as-is when the caller already built it
480
+ // (fe-landing's transformSearchResultItem); no fallback assembly here, since the exact
481
+ // seoFields/highlights order is caller-specific (see AutobahnCar.seoText's own comment).
482
+ ...(typeof (storeCar as any).seoText === 'string' && (storeCar as any).seoText ? { seoText: (storeCar as any).seoText } : {}),
479
483
  ...(listPrice != null ? { listPrice } : {}),
480
484
  ...(savings != null ? { savings } : {}),
481
485
  ...(oldMonthly != null && oldMonthly > monthly ? { oldMonthly } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.75",
3
+ "version": "1.0.77",
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",
@@ -1,62 +1,62 @@
1
- // Autobahn (auto.de redesign) — shared financing-number formatting.
2
- //
3
- // Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
4
- // Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
5
- // second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
6
- // Node service.
7
- //
8
- // The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
9
- // financing means monthly×term is NOT the total) and lives here once, not per-caller.
10
-
11
- /** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
12
- * toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
13
- export function formatEur(n: number): string {
14
- return n.toLocaleString('de-DE');
15
- }
16
-
17
- /** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
18
- export function formatPct(n: number): string {
19
- return `${n.toFixed(2).replace('.', ',')} %`;
20
- }
21
-
22
- export interface FinanceExampleFields {
23
- price: number;
24
- apr: number;
25
- down: number;
26
- /** Real config figure — falls back to price-down ONLY when genuinely absent. */
27
- netLoanAmount?: number;
28
- nominalRate?: number;
29
- finalInstallment?: number;
30
- grossAmount?: number;
31
- }
32
-
33
- export interface FinanceExampleLabels {
34
- vehiclePrice: string;
35
- priceSuffix: string;
36
- apr: string;
37
- nominal: string;
38
- net: string;
39
- down: string;
40
- final: string;
41
- gross: string;
42
- }
43
-
44
- /**
45
- * The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
46
- * included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
47
- * derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
48
- * `exampleLine`, byte-for-byte).
49
- */
50
- export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
51
- const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
52
- const parts: string[] = [
53
- `${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
54
- ...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
55
- ...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
56
- `${labels.net}: ${formatEur(netto)} €`,
57
- `${labels.down}: ${formatEur(fields.down)} €`,
58
- ...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
59
- ...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
60
- ];
61
- return `${footnoteMarker} ${parts.join('; ')};`;
62
- }
1
+ // Autobahn (auto.de redesign) — shared financing-number formatting.
2
+ //
3
+ // Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
4
+ // Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
5
+ // second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
6
+ // Node service.
7
+ //
8
+ // The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
9
+ // financing means monthly×term is NOT the total) and lives here once, not per-caller.
10
+
11
+ /** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
12
+ * toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
13
+ export function formatEur(n: number): string {
14
+ return n.toLocaleString('de-DE');
15
+ }
16
+
17
+ /** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
18
+ export function formatPct(n: number): string {
19
+ return `${n.toFixed(2).replace('.', ',')} %`;
20
+ }
21
+
22
+ export interface FinanceExampleFields {
23
+ price: number;
24
+ apr: number;
25
+ down: number;
26
+ /** Real config figure — falls back to price-down ONLY when genuinely absent. */
27
+ netLoanAmount?: number;
28
+ nominalRate?: number;
29
+ finalInstallment?: number;
30
+ grossAmount?: number;
31
+ }
32
+
33
+ export interface FinanceExampleLabels {
34
+ vehiclePrice: string;
35
+ priceSuffix: string;
36
+ apr: string;
37
+ nominal: string;
38
+ net: string;
39
+ down: string;
40
+ final: string;
41
+ gross: string;
42
+ }
43
+
44
+ /**
45
+ * The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
46
+ * included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
47
+ * derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
48
+ * `exampleLine`, byte-for-byte).
49
+ */
50
+ export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
51
+ const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
52
+ const parts: string[] = [
53
+ `${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
54
+ ...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
55
+ ...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
56
+ `${labels.net}: ${formatEur(netto)} €`,
57
+ `${labels.down}: ${formatEur(fields.down)} €`,
58
+ ...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
59
+ ...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
60
+ ];
61
+ return `${footnoteMarker} ${parts.join('; ')};`;
62
+ }
@@ -1,58 +1,58 @@
1
- // Autobahn (auto.de redesign) — Preisbewertung rating math, extracted from CommonUtils.ts.
2
- //
3
- // Pulled out on purpose: CommonUtils.ts transitively imports `../constants/Search`, which
4
- // `require()`s image assets (webpack-only — breaks any plain Node `require`/`tsc` consumer).
5
- // This file only imports `../constants/price` (zero further imports) — a pure, testable leaf
6
- // module. CommonUtils.ts re-exports these for existing consumers — one implementation, not a fork.
7
- // (image-generator's Meta AIA renderer vendors a copy of this math directly instead of depending
8
- // on this package — see services/image-generator/src/services/metaAia/vendoredAutobahnMath.ts —
9
- // so a plain-Node backend doesn't need to npm-install this whole design system's dependency tree.)
10
- import { priceRatings, priceRatingConfig } from '../constants/price';
11
- import { PREDICTABLE_PRICE_INDEX } from '../constants/core';
12
-
13
- export const getPriceRatingConfig = (currentPrice: number, pricePredicted: number) => { // tslint:disable-line
14
- const round = (value: number) => {
15
- return Number.isFinite(pricePredicted)
16
- ? Math.round((pricePredicted * (1 + (value / 100))) / 100) * 100 // tslint:disable-line
17
- : '???';
18
- };
19
-
20
- if (currentPrice === 0) {
21
- console.log('Empty current price');
22
- }
23
-
24
- return Object.keys(priceRatingConfig).reduce((acc: any, key: any) => {
25
- const { min, max } = priceRatingConfig[key];
26
- return { ...acc, [key]: { min: round(min), max: round(max) } };
27
- }, {}); // tslint:disable-line
28
- };
29
-
30
- export const getPredictablePrice = (currentSalesPrice: number, predictablePrice: number) => {
31
- if (Number.isFinite(Number.parseFloat(`${predictablePrice}`))) {
32
- return predictablePrice;
33
- }
34
-
35
- return currentSalesPrice * PREDICTABLE_PRICE_INDEX;
36
- }
37
-
38
- export const getPriceRating = (currentSalesPrice: number, predictablePrice: number) => {
39
- const isCorrectCurrentSalesPrice = Number.isFinite(Number.parseFloat(`${currentSalesPrice}`));
40
- // const isCorrectPredictablePrice = Number.isFinite(Number.parseFloat(`${predictablePrice}`));
41
-
42
- if (!isCorrectCurrentSalesPrice) return 0;
43
-
44
-
45
- const validatedPredictablePrice = getPredictablePrice(currentSalesPrice, predictablePrice);
46
-
47
- const ratingConfig = getPriceRatingConfig(currentSalesPrice, validatedPredictablePrice);
48
-
49
- for (const key of Object.keys(ratingConfig)) {
50
- const { min, max } = ratingConfig[key];
51
- const TOP_PRICE = 5;
52
- if (key === 'highPrice' && currentSalesPrice === max) return 1;
53
- if (key === 'topPrice' && currentSalesPrice <= min) return TOP_PRICE;
54
- if (currentSalesPrice >= min && currentSalesPrice < max) return priceRatings.indexOf(key);
55
- }
56
-
57
- return 0;
58
- };
1
+ // Autobahn (auto.de redesign) — Preisbewertung rating math, extracted from CommonUtils.ts.
2
+ //
3
+ // Pulled out on purpose: CommonUtils.ts transitively imports `../constants/Search`, which
4
+ // `require()`s image assets (webpack-only — breaks any plain Node `require`/`tsc` consumer).
5
+ // This file only imports `../constants/price` (zero further imports) — a pure, testable leaf
6
+ // module. CommonUtils.ts re-exports these for existing consumers — one implementation, not a fork.
7
+ // (image-generator's Meta AIA renderer vendors a copy of this math directly instead of depending
8
+ // on this package — see services/image-generator/src/services/metaAia/vendoredAutobahnMath.ts —
9
+ // so a plain-Node backend doesn't need to npm-install this whole design system's dependency tree.)
10
+ import { priceRatings, priceRatingConfig } from '../constants/price';
11
+ import { PREDICTABLE_PRICE_INDEX } from '../constants/core';
12
+
13
+ export const getPriceRatingConfig = (currentPrice: number, pricePredicted: number) => { // tslint:disable-line
14
+ const round = (value: number) => {
15
+ return Number.isFinite(pricePredicted)
16
+ ? Math.round((pricePredicted * (1 + (value / 100))) / 100) * 100 // tslint:disable-line
17
+ : '???';
18
+ };
19
+
20
+ if (currentPrice === 0) {
21
+ console.log('Empty current price');
22
+ }
23
+
24
+ return Object.keys(priceRatingConfig).reduce((acc: any, key: any) => {
25
+ const { min, max } = priceRatingConfig[key];
26
+ return { ...acc, [key]: { min: round(min), max: round(max) } };
27
+ }, {}); // tslint:disable-line
28
+ };
29
+
30
+ export const getPredictablePrice = (currentSalesPrice: number, predictablePrice: number) => {
31
+ if (Number.isFinite(Number.parseFloat(`${predictablePrice}`))) {
32
+ return predictablePrice;
33
+ }
34
+
35
+ return currentSalesPrice * PREDICTABLE_PRICE_INDEX;
36
+ }
37
+
38
+ export const getPriceRating = (currentSalesPrice: number, predictablePrice: number) => {
39
+ const isCorrectCurrentSalesPrice = Number.isFinite(Number.parseFloat(`${currentSalesPrice}`));
40
+ // const isCorrectPredictablePrice = Number.isFinite(Number.parseFloat(`${predictablePrice}`));
41
+
42
+ if (!isCorrectCurrentSalesPrice) return 0;
43
+
44
+
45
+ const validatedPredictablePrice = getPredictablePrice(currentSalesPrice, predictablePrice);
46
+
47
+ const ratingConfig = getPriceRatingConfig(currentSalesPrice, validatedPredictablePrice);
48
+
49
+ for (const key of Object.keys(ratingConfig)) {
50
+ const { min, max } = ratingConfig[key];
51
+ const TOP_PRICE = 5;
52
+ if (key === 'highPrice' && currentSalesPrice === max) return 1;
53
+ if (key === 'topPrice' && currentSalesPrice <= min) return TOP_PRICE;
54
+ if (currentSalesPrice >= min && currentSalesPrice < max) return priceRatings.indexOf(key);
55
+ }
56
+
57
+ return 0;
58
+ };