fix(kvytky): combo cards driven by ezy API — no test tariffs, correct prices
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions

buildComboCards now uses ezy tariffs as primary source when available.
Static cards are only used for descriptions and as fallback when ezy is down.
This removes test/placeholder cards and fixes mismatched prices from CMS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-06-08 14:56:23 +01:00
parent 12cd941da5
commit 8ffd6ebaa6

View file

@ -210,42 +210,49 @@ const COMBO_CARDS_STATIC: ComboCardData[] = [
const parsePrice = (s: string): number => Number(String(s).replace(/[^\d]/g, '')) || 0
function buildComboCards(pageData: TicketsPage | null, tariffs: Tariff[]): ComboCardData[] {
const cms = pageData?.comboCards
const cards: ComboCardData[] =
!cms || cms.length === 0
? COMBO_CARDS_STATIC
: cms.map((c, i) => ({
id: c.id ?? `combo-${i}`,
tariffId: `combo-${i}`,
priceValue: parsePrice(c.price),
name: c.name,
subtitle: c.subtitle ?? null,
price: c.price,
description: c.description ?? '',
featured: !!c.featured,
badge: c.badge ?? null,
locations: (c.locations ?? []).map((l) => l.text).filter(Boolean) as string[],
}))
// Match combo cards to live combo tariffs (by ascending price) to get a real
// cart tariffId + current price for the cart/checkout flow.
const apiCombos = tariffs
.filter((t) => t.categoryTag === 'combo')
.sort((a, b) => a.price - b.price)
if (apiCombos.length === 0) return cards
const sorted = [...cards].sort((a, b) => a.priceValue - b.priceValue)
const overrides = new Map<string, { tariffId: string; priceValue: number; price: string }>()
sorted.forEach((card, i) => {
const api = apiCombos[i]
if (api)
overrides.set(card.id, {
// When ezy has live combo tariffs, use them as the authoritative source for
// names and prices. Match descriptions/badges from static cards by name prefix.
if (apiCombos.length > 0) {
return apiCombos.map((api, i) => {
const nameKey = api.name.slice(0, 7).toLowerCase()
const staticMatch =
COMBO_CARDS_STATIC.find((c) =>
`${c.name} ${c.subtitle ?? ''}`.toLowerCase().includes(nameKey)
) ?? COMBO_CARDS_STATIC[Math.min(i, COMBO_CARDS_STATIC.length - 1)]!
return {
id: `combo-${api.id}`,
tariffId: String(api.id),
priceValue: api.price,
name: api.name,
subtitle: null,
price: `${api.price}`,
})
})
return cards.map((c) => ({ ...c, ...(overrides.get(c.id) ?? {}) }))
description: staticMatch.description,
featured: i === 1,
badge: i === 1 ? 'Найпопулярніший' : null,
locations: COMBO_LOCATIONS,
}
})
}
// No ezy data — fall back to CMS (excluding test/placeholder cards) or static.
const cms = pageData?.comboCards?.filter((c) => parsePrice(c.price) > 0 && c.name.length > 2)
if (!cms || cms.length === 0) return COMBO_CARDS_STATIC
return cms.map((c, i) => ({
id: c.id ?? `combo-${i}`,
tariffId: `combo-${i}`,
priceValue: parsePrice(c.price),
name: c.name,
subtitle: c.subtitle ?? null,
price: c.price,
description: c.description ?? '',
featured: !!c.featured,
badge: c.badge ?? null,
locations: (c.locations ?? []).map((l) => l.text).filter(Boolean) as string[],
}))
}
// ─────────────────────────────────────────────