Apps, web, i18n, UI, partners, payouts, backoffice, bariki, prod infra

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zabudko-ruandClaude Opus 5.5 committed 2026-10-25 22:55:29 +04:00
1 parent 45d7512d2b
commit 3489e0b816
319 files changed
+45483 -121

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
import '../../variants.css';
import { quote } from '@barev/core';
import { createT, formatAmd, isLocale, LOCALES, type Locale } from '@barev/i18n';
import { CATEGORY_ICON, Icon, type CategoryCode } from '@barev/ui';
import { notFound } from 'next/navigation';
import { BotLink, BotLinkProvider } from '../../../components/client';
import { VariantHeader, variantMetadata } from '../../../components/variant';
import { getCatalog, getCleaning } from '../../../lib/data';
export const revalidate = 300;
export const dynamicParams = false;
export function generateStaticParams() {
return LOCALES.map((locale) => ({ locale }));
}
export function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
return variantMetadata(params, '/land1', 'landing.v1.title');
}
/** Версия 1 «Чат»: продукт живёт в Telegram, поэтому страница — переписка с ботом, кнопки — как inline-клавиатура */
export default async function Land1({ params }: { params: Promise<{ locale: string }> }) {
const { locale: raw } = await params;
if (!isLocale(raw)) notFound();
const locale: Locale = raw;
const t = createT(locale);
const amd = (n: number) => formatAmd(locale, n);
const [catalog, cleaning] = await Promise.all([getCatalog(), getCleaning()]);
// цена в диалоге — настоящая: обычная уборка, 2 комнаты, 1 санузел
const demoPrice = quote(cleaning.pricing, { cleaningType: 'regular', rooms: 2, bathrooms: 1 }).totalPrice;
const groups = catalog.categories.filter((c) => c.code !== 'PERKS' && c.services.length > 0);
const chat = [
{ from: 'me', text: t('landing.v1.u1'), time: '10:02' },
{ from: 'bot', text: t('landing.v1.b1', { price: amd(demoPrice) }), time: '10:02' },
{ from: 'me', text: t('landing.v1.u2'), time: '10:03' },
{ from: 'bot', text: t('landing.v1.b2'), time: '10:03' },
] as const;
return (
<BotLinkProvider locale={locale} bot={catalog.botUsername}>
<div className="v1">
<div className="v-wrap">
<VariantHeader locale={locale} path="/land1" />
<section className="v1-hero">
<div className="v1-hero__text">
<h1 className="v1-title">{t('landing.v1.title')}</h1>
<p className="v1-sub">{t('landing.v1.sub')}</p>
</div>
<div className="v1-chat" aria-label={t('landing.v1.botName')}>
<div className="v1-chat__head">
<span className="v1-avatar" aria-hidden="true">
B
</span>
<span>
<b>{t('landing.v1.botName')}</b>
<small>{t('landing.v1.botStatus')}</small>
</span>
</div>
<ol className="v1-chat__log">
{chat.map((m, i) => (
<li key={i} className={`v1-msg v1-msg--${m.from}`} style={{ ['--d' as string]: `${0.3 + i * 0.9}s` }}>
{m.text}
<time>{m.time}</time>
</li>
))}
</ol>
{/* как inline-клавиатура под последним сообщением бота */}
<div className="v1-keys" style={{ ['--d' as string]: `${0.3 + chat.length * 0.9}s` }}>
<a href={`/${locale}/clean`} className="v1-key v1-key--main">
{t('landing.hero.cleanCta')}
</a>
<BotLink className="v1-key">{t('landing.v1.cta')}</BotLink>
</div>
</div>
</section>
<section className="v1-card">
<h2 className="v1-h2">{t('landing.v1.canTitle')}</h2>
<p className="v1-muted">{t('landing.v1.canText')}</p>
<div className="v1-groups">
{groups.map((g) => (
<div key={g.code} className="v1-group">
<h3>
<Icon name={CATEGORY_ICON[g.code as CategoryCode]} size={20} />
{t(`categories.${g.code}.name`)}
</h3>
<div className="v1-group__keys">
{g.services.map((s) =>
s.code === 'cleaning' && s.active ? (
<a key={s.code} href={`/${locale}/clean`} className="v1-svc v1-svc--live">
<span>{t(`services.${s.code}.name`)}</span>
<span>{t('common.from', { price: amd(s.fromPrice) })}</span>
</a>
) : (
<div key={s.code} className="v1-svc">
<span>{t(`services.${s.code}.name`)}</span>
<span>{s.active ? t('common.from', { price: amd(s.fromPrice) }) : t('common.soon')}</span>
</div>
),
)}
</div>
</div>
))}
</div>
</section>
<section className="v1-why">
<h2 className="v1-h2">{t('landing.v1.whyTitle')}</h2>
<div className="v1-why__grid">
{([1, 2, 3] as const).map((n) => (
<div key={n} className="v1-why__item">
<b>{t(`landing.v1.why${n}`)}</b>
<span>{t(`landing.v1.why${n}Sub`)}</span>
</div>
))}
</div>
</section>
<section className="v1-final">
<div>
<h2 className="v1-final__title">{t('landing.v1.ctaTitle')}</h2>
<p>{t('landing.v1.ctaText')}</p>
</div>
{/* поле ввода сообщения: «Barev!» уже набрано, кнопка отправки открывает бота */}
<BotLink className="v1-compose">
<span className="v1-compose__text">Barev!</span>
<span className="v1-compose__send" aria-hidden="true">
<Icon name="arrow" size={20} strokeWidth={2.4} />
</span>
<span className="v-sr">{t('landing.v1.cta')}</span>
</BotLink>
</section>
<footer className="v-footer">
<span>{t('landing.footer.brand')}</span>
<a href={`/${locale}/privacy`}>{t('landing.footer.privacy')}</a>
</footer>
</div>
</div>
</BotLinkProvider>
);
}