Files
BarevClub/apps/miniapp-pro/src/App.tsx
T

96 lines
4.3 KiB
TypeScript

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>
);
}