Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
106 lines
4.4 KiB
TypeScript
106 lines
4.4 KiB
TypeScript
import { CATALOG, CATEGORY_CODES, DEFAULT_CATEGORY_DISCOUNTS, DEFAULT_SETTINGS, fromPrice, YEREVAN_DISTRICTS, type CategoryCode, type ParamsSchema, type Pricing, type SlotConfig } from '@barev/core';
|
|
|
|
export interface LandingCatalog {
|
|
categories: { code: CategoryCode; barevPlusDiscountPct: number; services: { code: string; fromPrice: number; active: boolean }[] }[];
|
|
barevPlus: { monthlyPrice: number; giftDays: number };
|
|
/** цены и скидки главной из настроек (админка → Настройки) */
|
|
offer: LandingOffer;
|
|
agency: { commissionPct: number; attributionMonths: number };
|
|
botUsername: string;
|
|
}
|
|
|
|
export interface LandingOffer {
|
|
plusMaxPct: number;
|
|
elitePrice: number;
|
|
eliteMaxPct: number;
|
|
hotelWithPlus: number;
|
|
hotelBase: number;
|
|
consultation: number;
|
|
docsFrom: number;
|
|
/** страницы услуг: /social-card, /residence-permit, /airport */
|
|
socialCard: number;
|
|
residenceEscortFrom: number;
|
|
residenceStateFee: number;
|
|
transferStandard: number;
|
|
transferPremium: number;
|
|
}
|
|
|
|
/** Ключи настроек → поля для лендинга; то же отдаёт public.catalog */
|
|
export function offerFromSettings(s: typeof DEFAULT_SETTINGS): LandingOffer {
|
|
return {
|
|
plusMaxPct: s['barevPlus.maxDiscountPct'],
|
|
elitePrice: s['elite.monthlyPrice'],
|
|
eliteMaxPct: s['elite.maxDiscountPct'],
|
|
hotelWithPlus: s['relocation.hotelWithPlus'],
|
|
hotelBase: s['relocation.hotelBase'],
|
|
consultation: s['relocation.consultation'],
|
|
docsFrom: s['docs.fromPrice'],
|
|
socialCard: s['docs.socialCardPrice'],
|
|
residenceEscortFrom: s['residence.escortFrom'],
|
|
residenceStateFee: s['residence.stateFee'],
|
|
transferStandard: s['transfer.standardPrice'],
|
|
transferPremium: s['transfer.premiumPrice'],
|
|
};
|
|
}
|
|
|
|
const API = process.env.API_URL ?? 'http://localhost:4000';
|
|
|
|
/** Запасной вариант, если API недоступно при сборке: цены-заглушки из каталога */
|
|
function fallback(): LandingCatalog {
|
|
return {
|
|
categories: CATEGORY_CODES.map((code) => ({
|
|
code,
|
|
barevPlusDiscountPct: DEFAULT_CATEGORY_DISCOUNTS[code],
|
|
services: CATALOG.filter((s) => s.category === code).map((s) => ({ code: s.code, fromPrice: fromPrice(s.pricing), active: s.stage === 1 })),
|
|
})),
|
|
barevPlus: { monthlyPrice: DEFAULT_SETTINGS['barevPlus.monthlyPrice'], giftDays: DEFAULT_SETTINGS['barevPlus.giftDays'] },
|
|
offer: offerFromSettings(DEFAULT_SETTINGS),
|
|
agency: { commissionPct: DEFAULT_SETTINGS['agency.defaultCommissionPct'], attributionMonths: DEFAULT_SETTINGS['agency.attributionMonths'] },
|
|
botUsername: process.env.CLIENT_BOT_USERNAME ?? 'barev_club_bot',
|
|
};
|
|
}
|
|
|
|
/** LND-5: цены и состав услуг — из справочника (ISR, обновление раз в 5 минут) */
|
|
export async function getCatalog(): Promise<LandingCatalog> {
|
|
try {
|
|
const res = await fetch(`${API}/trpc/public.catalog`, { next: { revalidate: 300 } });
|
|
if (!res.ok) throw new Error(String(res.status));
|
|
const body = (await res.json()) as { result: { data: { json: LandingCatalog } } };
|
|
// старое API без offer или без новых полей — значения по умолчанию
|
|
return { ...body.result.data.json, offer: { ...offerFromSettings(DEFAULT_SETTINGS), ...body.result.data.json.offer } };
|
|
} catch {
|
|
return fallback();
|
|
}
|
|
}
|
|
|
|
export interface CleaningConfig {
|
|
active: boolean;
|
|
pricing: Pricing;
|
|
paramsSchema: ParamsSchema;
|
|
slotConfig: SlotConfig;
|
|
categoryDiscountPct: number;
|
|
districts: string[];
|
|
botUsername: string;
|
|
}
|
|
|
|
/** Страница /clean: правила цены из справочника (ISR); без API — значения из каталога в коде */
|
|
export async function getCleaning(): Promise<CleaningConfig> {
|
|
try {
|
|
const res = await fetch(`${API}/trpc/public.cleaning`, { next: { revalidate: 300 } });
|
|
if (!res.ok) throw new Error(String(res.status));
|
|
const body = (await res.json()) as { result: { data: { json: CleaningConfig } } };
|
|
return body.result.data.json;
|
|
} catch {
|
|
const s = CATALOG.find((c) => c.code === 'cleaning')!;
|
|
return {
|
|
active: true,
|
|
pricing: s.pricing,
|
|
paramsSchema: s.paramsSchema,
|
|
slotConfig: s.slotConfig,
|
|
categoryDiscountPct: DEFAULT_CATEGORY_DISCOUNTS.HOME,
|
|
districts: [...YEREVAN_DISTRICTS],
|
|
botUsername: process.env.CLIENT_BOT_USERNAME ?? 'barev_club_bot',
|
|
};
|
|
}
|
|
}
|