Profile with club card, plum theme for buttons/selections and the sticky total, move screens with the full-width hero, services/docs headers, empty docs fix. Promo code field: applies to the quote, shows why a code is rejected, says when the membership discount is bigger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
76 lines
3.2 KiB
TypeScript
76 lines
3.2 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { CATEGORY_ICON, Center, Icon, Spinner, type CategoryCode } from '@barev/ui';
|
|
import { useI18n, useLocation, useTRPC } from '@barev/web';
|
|
import { useEffect } from 'react';
|
|
import { levelDiscount, toneClass, type Level } from '../lib.tsx';
|
|
|
|
/** Каталог: направления со скидкой по уровню клиента; услуги без онлайн-записи — заявка «перезвоните» */
|
|
export function Services() {
|
|
const { t, amd } = useI18n();
|
|
const trpc = useTRPC();
|
|
const { query } = useLocation();
|
|
const focus = query.get('c');
|
|
const catalog = useQuery(trpc.client.catalog.queryOptions());
|
|
|
|
useEffect(() => {
|
|
if (focus && catalog.data) document.getElementById(`cat-${focus}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}, [focus, catalog.data]);
|
|
|
|
if (!catalog.data)
|
|
return (
|
|
<Center>
|
|
<Spinner />
|
|
</Center>
|
|
);
|
|
const level = catalog.data.level as Level;
|
|
return (
|
|
<main className="app">
|
|
<header className="screen-head">
|
|
<h1 className="screen-head__title">{t('client.services.title')}</h1>
|
|
</header>
|
|
{catalog.data.categories
|
|
.filter((c) => c.code !== 'PERKS')
|
|
.map((c) => (
|
|
<section key={c.code} id={`cat-${c.code}`} className={`bc-card ${toneClass(c.code)}`} style={{ padding: 20, scrollMarginTop: 12 }}>
|
|
<div className="bc-between" style={{ marginBottom: 12 }}>
|
|
<h2 className="bc-h2">{t(`categories.${c.code}.name`)}</h2>
|
|
<Icon name={CATEGORY_ICON[c.code as CategoryCode]} size={26} />
|
|
</div>
|
|
<div className="bc-stack" style={{ gap: 6 }}>
|
|
{c.services.map((s) =>
|
|
s.active ? (
|
|
<a key={s.code} href={`#/order/${s.code}`} className="bc-row-pill">
|
|
<span style={{ fontWeight: 600 }}>{t(`services.${s.code}.name`)}</span>
|
|
<span className="bc-row">
|
|
{t('common.from', { price: amd(s.fromPrice) })}
|
|
<Icon name="arrow" size={16} />
|
|
</span>
|
|
</a>
|
|
) : (
|
|
<a key={s.code} href={`#/request/${s.code}`} className="bc-row-pill">
|
|
<span style={{ fontWeight: 600 }}>{t(`services.${s.code}.name`)}</span>
|
|
<span className="bc-row bc-small">
|
|
{t('client.services.callback')}
|
|
<Icon name="arrow" size={16} />
|
|
</span>
|
|
</a>
|
|
),
|
|
)}
|
|
</div>
|
|
{levelDiscount(level, c) > 0 ? (
|
|
<div className="bc-small" style={{ marginTop: 10, fontWeight: 600 }}>
|
|
{t('client.services.yourDiscount', { pct: levelDiscount(level, c) })}
|
|
</div>
|
|
) : (
|
|
c.eliteDiscountPct > 0 && (
|
|
<a href="#/membership" className="bc-small" style={{ display: 'block', marginTop: 10, fontWeight: 600, color: 'inherit' }}>
|
|
{t('client.services.withElite', { pct: c.eliteDiscountPct })}
|
|
</a>
|
|
)
|
|
)}
|
|
</section>
|
|
))}
|
|
</main>
|
|
);
|
|
}
|