Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
144 lines
6.3 KiB
TypeScript
144 lines
6.3 KiB
TypeScript
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>
|
|
);
|
|
}
|