@bytebrand/fe-ui-core-autobahn 1.0.79 → 1.0.81

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.
@@ -27,7 +27,7 @@ import { TranslationFunction } from '../types';
27
27
  import { Button, Dropdown, DropdownOption, Icon, Spec, StatChip, Stars } from '../ui';
28
28
  import { CO2_CLASSES, CO2_COLORS, TIER_LABELS, TIER_KEYS, TIER_COLORS, TIER_BACKGROUNDS } from '../constants';
29
29
  import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview } from './types';
30
- import { CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips, ToggleRow } from './FinanceCalculator';
30
+ import { AddServicesTooltipBody, CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips, ToggleRow } from './FinanceCalculator';
31
31
  import { formatEur as eur, formatPct as pct } from '../utils/financeFormat';
32
32
 
33
33
  /** Figma "check with open arc" glyph (modern lucide circle-check-big; the bundled lucide
@@ -1460,6 +1460,12 @@ export interface VdpBottomBarProps {
1460
1460
  ratingLabel?: string;
1461
1461
  /** primary CTA click. */
1462
1462
  onSecure?: () => void;
1463
+ /** true once the customer already has an offer request/checkout in progress for this car —
1464
+ * turns the primary CTA green (see .fin-cta-secured) instead of the default filled style. */
1465
+ secured?: boolean;
1466
+ /** overrides the primary CTA label while `secured` is true (e.g. "Zur Bestellung"). Ignored
1467
+ * when `secured` is false — the CTA keeps its own default label in that case. */
1468
+ secureLabel?: string;
1463
1469
  /** phone CTA click. */
1464
1470
  onCall?: () => void;
1465
1471
  /** opens the "Rate anpassen" sheet; when omitted the button is not rendered. */
@@ -1488,6 +1494,8 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
1488
1494
  oldMonthly,
1489
1495
  ratingLabel,
1490
1496
  onSecure,
1497
+ secured,
1498
+ secureLabel,
1491
1499
  onCall,
1492
1500
  onOpenAdjust,
1493
1501
  down,
@@ -1501,10 +1509,10 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
1501
1509
  const rate = monthly != null ? monthly : vehicle.monthly;
1502
1510
  const tierIdx = TIER_LABELS.indexOf(ratingLabel as (typeof TIER_LABELS)[number]);
1503
1511
  const evalTier = tierIdx >= 0 ? tierIdx : 1;
1504
- // "good" collapses into the same "Top-Preis" label as the actual top tier (superscript marks
1505
- // the collapse) — identical convention to CarCard's PriceEvalCol / FinanceCalculator's pill.
1506
1512
  const isGoodTier = TIER_KEYS[evalTier] === 'good';
1507
- const evalTierLabel = isGoodTier ? t('autobahn:tier.top') : t('autobahn:tier.' + TIER_KEYS[evalTier]);
1513
+ // each tier shows its own real label now (Top/Guter/Fairer/Erhöht/Hoher Preis) — no more
1514
+ // collapsing 'good' onto the "Top-Preis" claim. Pill styling (colour/bg/class/footnote) unchanged.
1515
+ const evalTierLabel = t('autobahn:tier.' + TIER_KEYS[evalTier]);
1508
1516
  const evalPillColor = isGoodTier ? TIER_COLORS[0] : TIER_COLORS[evalTier];
1509
1517
  const evalPillBackground = isGoodTier ? TIER_BACKGROUNDS[0] : TIER_BACKGROUNDS[evalTier];
1510
1518
  // condition summary — same wording/order as FinanceCalculator's own .fin-cond (financing
@@ -1713,6 +1721,7 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
1713
1721
  <div className="bb-cta-row">
1714
1722
  <Button
1715
1723
  variant="filled"
1724
+ className={secured ? 'fin-cta-secured' : undefined}
1716
1725
  style={{
1717
1726
  width: isCash ? undefined : 180,
1718
1727
  flex: isCash ? 1 : undefined,
@@ -1730,8 +1739,8 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
1730
1739
  }}
1731
1740
  onClick={onSecure}
1732
1741
  >
1733
- <DocCheckIcon size={23} />
1734
- {t('autobahn:bottom.secureOffer')}
1742
+ {secured ? <Icon name="shield-check" size={18} /> : <DocCheckIcon size={23} />}
1743
+ {secured && secureLabel ? secureLabel : t('autobahn:bottom.secureOffer')}
1735
1744
  </Button>
1736
1745
  {/* padding:0 — icon-only button (same fix as the sheet's phone button) */}
1737
1746
  {onCall && (
@@ -2192,18 +2201,172 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2192
2201
  {/* ============================== DESKTOP (≥601px) — always-expanded breakdown, no tabs
2193
2202
  (the desktop .fin-calc-card owns its own tabs outside this modal) ============================== */}
2194
2203
  <div className="hide-mobile">
2195
- <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 14 }}>
2196
- <span className="num" style={{ fontSize: 34, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-1px' }}>
2197
- {eur(monthly)} €{oldMonthly != null && <> <s className="fin-rate-old">{eur(Math.round(oldMonthly))} €</s></>}
2198
- </span>
2199
- <span className="md-label-sm">{t('autobahn:finance.perMonth')}</span>
2204
+ <div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')} style={{ marginBottom: 14 }}>
2205
+ <div className="fin-rate-main">
2206
+ {isCash && listPrice != null && listPrice > price && (
2207
+ <div
2208
+ style={{ display: 'flex', justifyContent: 'flex-end', lineHeight: 1, marginTop: 0 }}
2209
+ role="button"
2210
+ tabIndex={0}
2211
+ onClick={() => setUvpTooltipOpen((v) => !v)}
2212
+ >
2213
+ <s className="num fin-rate-old fin-rate-old-list">
2214
+ {eur(listPrice)} €
2215
+ <span
2216
+ className="pp-eyebrow-badge"
2217
+ role="button"
2218
+ tabIndex={0}
2219
+ onClick={(e) => {
2220
+ e.stopPropagation();
2221
+ setUvpTooltipOpen((v) => !v);
2222
+ }}
2223
+ >
2224
+ 2
2225
+ </span>
2226
+ {uvpTooltipOpen && (
2227
+ <div className="pp-uvp-tooltip" onClick={(e) => e.stopPropagation()}>
2228
+ <button
2229
+ type="button"
2230
+ className="pp-uvp-tooltip-close"
2231
+ aria-label={t('autobahn:modal.close')}
2232
+ onClick={(e) => {
2233
+ e.stopPropagation();
2234
+ setUvpTooltipOpen(false);
2235
+ }}
2236
+ >
2237
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2238
+ <path d="M5 5L15 15M15 5L5 15" stroke="white" strokeWidth={2.4} strokeLinecap="round" />
2239
+ </svg>
2240
+ </button>
2241
+ <div className="pp-uvp-tooltip-body">{t('autobahn:card.price.uvpListTooltipBody')}</div>
2242
+ </div>
2243
+ )}
2244
+ </s>
2245
+ </div>
2246
+ )}
2247
+ <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
2248
+ {isCash ? (
2249
+ <span
2250
+ className={`num fin-rate-cash-mobile${
2251
+ (() => {
2252
+ const digits = String(Math.round(price)).length;
2253
+ if (digits >= 8) return ' fin-rate-cash-mobile--len8';
2254
+ if (digits === 7) return ' fin-rate-cash-mobile--len7';
2255
+ if (digits === 6) return ' fin-rate-cash-mobile--len6';
2256
+ return '';
2257
+ })()
2258
+ }`}
2259
+ style={{ fontSize: 45, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-2px' }}
2260
+ >
2261
+ {eur(price)} €<sup>1</sup>
2262
+ </span>
2263
+ ) : (
2264
+ <>
2265
+ <span
2266
+ className={`num fin-rate-hero${
2267
+ (() => {
2268
+ const digits = String(Math.round(monthly)).length;
2269
+ if (digits >= 6) return ' fin-rate-hero--len6';
2270
+ if (digits === 5) return ' fin-rate-hero--len5';
2271
+ if (digits === 4) return ' fin-rate-hero--len4';
2272
+ return '';
2273
+ })()
2274
+ }`}
2275
+ style={{ fontSize: 65, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-2px' }}
2276
+ >
2277
+ {eur(Math.round(monthly))}<span style={{ marginLeft: 6 }}>€</span>
2278
+ </span>
2279
+ <span className="fin-rate-unit">
2280
+ {oldMonthly != null && (
2281
+ <span
2282
+ style={{ display: 'flex', flexDirection: 'column', cursor: 'pointer' }}
2283
+ role="button"
2284
+ tabIndex={0}
2285
+ onClick={() => setUvpTooltipOpen((v) => !v)}
2286
+ >
2287
+ <span className="pp-eyebrow">
2288
+ {t('autobahn:card.price.uvpRateLabel')}
2289
+ <span
2290
+ className="pp-eyebrow-badge"
2291
+ role="button"
2292
+ tabIndex={0}
2293
+ onClick={(e) => {
2294
+ e.stopPropagation();
2295
+ setUvpTooltipOpen((v) => !v);
2296
+ }}
2297
+ >
2298
+ 3
2299
+ </span>
2300
+ {uvpTooltipOpen && (
2301
+ <div
2302
+ className={'pp-uvp-tooltip' + (mode === 'leasing' ? ' pp-uvp-tooltip--leasing' : '')}
2303
+ onClick={(e) => e.stopPropagation()}
2304
+ >
2305
+ <button
2306
+ type="button"
2307
+ className="pp-uvp-tooltip-close"
2308
+ aria-label={t('autobahn:modal.close')}
2309
+ onClick={(e) => {
2310
+ e.stopPropagation();
2311
+ setUvpTooltipOpen(false);
2312
+ }}
2313
+ >
2314
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2315
+ <path d="M5 5L15 15M15 5L5 15" stroke="white" strokeWidth={2.4} strokeLinecap="round" />
2316
+ </svg>
2317
+ </button>
2318
+ <div className="pp-uvp-tooltip-body">
2319
+ {t(
2320
+ mode === 'leasing'
2321
+ ? 'autobahn:card.price.uvpRateTooltipBodyLeasing'
2322
+ : 'autobahn:card.price.uvpRateTooltipBody',
2323
+ )}
2324
+ </div>
2325
+ </div>
2326
+ )}
2327
+ </span>
2328
+ <s
2329
+ className={`num fin-rate-old fin-rate-example-price${
2330
+ String(Math.round(oldMonthly)).length >= 5
2331
+ ? ' fin-rate-example-price--len5'
2332
+ : String(Math.round(oldMonthly)).length === 4
2333
+ ? ' fin-rate-example-price--len4'
2334
+ : ''
2335
+ }`}
2336
+ >
2337
+ {eur(Math.round(oldMonthly))} €
2338
+ </s>
2339
+ </span>
2340
+ )}
2341
+ <span className="fin-rate-example-suffix">
2342
+ {t('autobahn:finance.perMonth')}
2343
+ </span>
2344
+ </span>
2345
+ </>
2346
+ )}
2347
+ </div>
2348
+ </div>
2349
+ <div className="fin-eval-col">
2350
+ <div className="fin-eval-pill-cell">
2351
+ {ratingLabel && (
2352
+ <span
2353
+ className={`fin-eval-pill${TIER_KEYS[evalTier] === 'good' ? ' fin-eval-pill--good' : ''}`}
2354
+ style={{ color: TIER_COLORS[evalTier], background: `color-mix(in srgb, ${TIER_COLORS[evalTier]} 16%, transparent)` }}
2355
+ >
2356
+ {evalTierLabel}
2357
+ {TIER_KEYS[evalTier] === 'good' && <sup>4</sup>}
2358
+ </span>
2359
+ )}
2360
+ </div>
2361
+ <div className="fin-eval-bar-cell">{ratingLabel && <EvalBar tier={evalTier} />}</div>
2362
+ </div>
2200
2363
  </div>
2201
2364
  {/* adjustment sliders — change a value and every figure below recomputes live (via the page) */}
2202
2365
  {adjustable && (
2203
2366
  <div style={{ display: 'flex', flexDirection: 'column', gap: 16, marginBottom: 18 }}>
2204
2367
  <div>
2205
2368
  <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
2206
- <span className="md-title-sm">{t('autobahn:finance.downPaymentSliderLabel')}</span>
2369
+ <span className="md-title-sm">{t('autobahn:finance.downPaymentSliderLabelSheet')}</span>
2207
2370
  <span className="num" style={{ fontWeight: 700 }}>
2208
2371
  {eur(down)} €
2209
2372
  </span>
@@ -2266,6 +2429,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2266
2429
  checked={!!withAddServices}
2267
2430
  disabled={addServicesDisabled}
2268
2431
  onChange={onToggleAddServices}
2432
+ tooltip={<AddServicesTooltipBody t={t} />}
2433
+ tooltipCloseLabel={t('autobahn:modal.close')}
2269
2434
  />
2270
2435
  )}
2271
2436
  <div ref={desktopBreakdownRef} style={!isCash ? { marginTop: 18 } : undefined}>
@@ -2564,7 +2729,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2564
2729
  </div>
2565
2730
  )}
2566
2731
 
2567
- <div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')}>
2732
+ <div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')} style={{ paddingBottom: 10 }}>
2568
2733
  <div className="fin-rate-main">
2569
2734
  {/* Figma Kaufen sheet: struck list price ABOVE the price, aligned to its right
2570
2735
  edge (inline-beside wraps — the 44px phone hero fills the whole column) */}
@@ -2728,12 +2893,9 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2728
2893
  {isCash ? (
2729
2894
  <>
2730
2895
  <div>
2731
- <CalcRow className="calcrow--payment" label={t('autobahn:finance.paymentLabel')}>
2896
+ <CalcRow className="calcrow--payment" label={t('autobahn:finance.paymentLabel')} last={!available}>
2732
2897
  {t('autobahn:finance.paymentValue')}
2733
2898
  </CalcRow>
2734
- <CalcRow className="calcrow--payment" label={t('autobahn:finance.warrantyLabel')} last={!available}>
2735
- {t('autobahn:finance.warrantyValue')}
2736
- </CalcRow>
2737
2899
  {/* Lieferung row disabled per request, replaced by real availability
2738
2900
  (carStore.isAvailable) — same "Sofort verfügbar" wording as VdpTrustRow.
2739
2901
  <CalcRow className="calcrow--payment" label={t('autobahn:finance.deliveryLabel')} last>
@@ -2755,6 +2917,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2755
2917
  disabled={addServicesDisabled}
2756
2918
  onChange={(checked) => onToggleAddServices && onToggleAddServices(checked)}
2757
2919
  staticNote={t('autobahn:finance.addServicesIncluded')}
2920
+ tooltip={<AddServicesTooltipBody t={t} />}
2921
+ tooltipCloseLabel={t('autobahn:modal.close')}
2758
2922
  />
2759
2923
  )}
2760
2924
  {/* pushed to the sheet's bottom edge — Figma keeps a big breathing gap above */}
@@ -2810,8 +2974,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2810
2974
  <div>
2811
2975
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
2812
2976
  <span className="md-title-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
2813
- {t('autobahn:finance.downPaymentSliderLabel')}
2814
- <Icon name="info" size={13} color="var(--md-on-surface-variant)" />
2977
+ {t('autobahn:finance.downPaymentSliderLabelSheet')}
2815
2978
  </span>
2816
2979
  <span className="num fin-down-value" style={{ fontWeight: 700, color: 'var(--md-primary)' }}>
2817
2980
  {eur(down)} €
@@ -2842,7 +3005,6 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2842
3005
  >
2843
3006
  <span className="md-title-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
2844
3007
  {t('autobahn:finance.annualMileageLabel')}
2845
- <Icon name="info" size={13} color="var(--md-on-surface-variant)" />
2846
3008
  </span>
2847
3009
  <span className="num" style={{ fontWeight: 700, color: 'var(--md-primary)' }}>
2848
3010
  {eur(annualMileage)} km
@@ -2891,6 +3053,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
2891
3053
  checked={!!withAddServices}
2892
3054
  disabled={addServicesDisabled}
2893
3055
  onChange={onToggleAddServices}
3056
+ tooltip={<AddServicesTooltipBody t={t} />}
3057
+ tooltipCloseLabel={t('autobahn:modal.close')}
2894
3058
  />
2895
3059
  )}
2896
3060