Snapshot of uncommitted work: landing, bots, prod infra, partner cabinet, backoffice stage 1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
45d7512d2b
commit
d452824c62
314 files changed
+44394
-121
No files matched your search
@@ -0,0 +1,105 @@
|
||||
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',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user