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,95 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Center, Icon, Spinner } from '@barev/ui';
|
||||
import { I18nProvider, match, navigate, pickLocale, telegramLanguage, useI18n, useLocation, useTelegramBack, useTRPC } from '@barev/web';
|
||||
import { useEffect, type ReactNode } from 'react';
|
||||
import { Company } from './screens/Company.tsx';
|
||||
import { History } from './screens/History.tsx';
|
||||
import { Job } from './screens/Job.tsx';
|
||||
import { Partner } from './screens/Partner.tsx';
|
||||
import { Today } from './screens/Today.tsx';
|
||||
|
||||
const ROOTS = ['/', '/history', '/company', '/partner'];
|
||||
|
||||
export function App({ hasInitData }: { hasInitData: boolean }) {
|
||||
const trpc = useTRPC();
|
||||
const me = useQuery({ ...trpc.pro.me.queryOptions(), enabled: hasInitData });
|
||||
const locale = pickLocale(me.data?.locale, telegramLanguage(), navigator.language);
|
||||
const status = me.error?.data?.httpStatus;
|
||||
return (
|
||||
<I18nProvider initial={locale}>
|
||||
{!hasInitData || status === 401 ? (
|
||||
<Message titleKey="pro.openInTelegram.title" textKey="pro.openInTelegram.text" />
|
||||
) : status === 403 ? (
|
||||
<Message titleKey="pro.notLinked.title" textKey="pro.notLinked.text" />
|
||||
) : me.isPending ? (
|
||||
<Center>
|
||||
<Spinner />
|
||||
</Center>
|
||||
) : me.error ? (
|
||||
<Message titleKey="errors.generic" textKey="common.retryLater" />
|
||||
) : (
|
||||
<Routes isProvider={!!me.data.provider} companies={me.data.companies} partners={me.data.partners} />
|
||||
)}
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function Routes({ isProvider, companies, partners }: { isProvider: boolean; companies: { id: string; name: string }[]; partners: { id: string; name: string; role: 'OWNER' | 'STAFF' }[] }) {
|
||||
const { path } = useLocation();
|
||||
useTelegramBack(path, ROOTS);
|
||||
// не исполнитель — сразу на экран компании или кабинет партнёра
|
||||
useEffect(() => {
|
||||
if (!isProvider && path === '/') navigate(companies.length ? '/company' : '/partner', { replace: true });
|
||||
}, [isProvider, path, companies.length]);
|
||||
|
||||
let screen: ReactNode;
|
||||
let m: Record<string, string> | null;
|
||||
if (path === '/' && isProvider) screen = <Today />;
|
||||
else if ((m = match('/orders/:id', path)) && isProvider) screen = <Job id={m.id!} />;
|
||||
else if (path === '/history' && isProvider) screen = <History />;
|
||||
else if (path === '/company' && companies.length) screen = <Company companies={companies} />;
|
||||
else if (path === '/partner' && partners.length) screen = <Partner partners={partners} />;
|
||||
else screen = isProvider ? <Today /> : companies.length ? <Company companies={companies} /> : <Partner partners={partners} />;
|
||||
// вкладки: Сегодня и История у исполнителя, Компания у диспетчера, Партнёр у собственника и сотрудников партнёра
|
||||
const tabs = (isProvider ? 2 : 0) + (companies.length ? 1 : 0) + (partners.length ? 1 : 0);
|
||||
const showNav = ROOTS.includes(path) && tabs > 1;
|
||||
return (
|
||||
<>
|
||||
{screen}
|
||||
{showNav && <Nav path={path} isProvider={isProvider} hasCompany={companies.length > 0} hasPartner={partners.length > 0} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Nav({ path, isProvider, hasCompany, hasPartner }: { path: string; isProvider: boolean; hasCompany: boolean; hasPartner: boolean }) {
|
||||
const { t } = useI18n();
|
||||
const item = (href: string, icon: Parameters<typeof Icon>[0]['name'], label: string) => (
|
||||
<a href={`#${href}`} aria-current={path === href ? 'page' : undefined}>
|
||||
<Icon name={icon} />
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
return (
|
||||
<nav className="bc-bottomnav" aria-label={t('pro.nav.label')}>
|
||||
{isProvider && item('/', 'clock', t('pro.nav.today'))}
|
||||
{isProvider && item('/history', 'star', t('pro.nav.history'))}
|
||||
{hasCompany && item('/company', 'grid', t('pro.nav.company'))}
|
||||
{hasPartner && item('/partner', 'gift', t('pro.nav.partner'))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function Message({ titleKey, textKey }: { titleKey: string; textKey: string }) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className="app app--plain">
|
||||
<Center>
|
||||
<div className="bc-stack" style={{ textAlign: 'center', alignItems: 'center' }}>
|
||||
<Icon name="logo" size={48} style={{ color: 'var(--bc-plum)' }} />
|
||||
<h1 className="bc-h2">{t(titleKey)}</h1>
|
||||
<p className="bc-muted">{t(textKey)}</p>
|
||||
</div>
|
||||
</Center>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user