feat(bots): M1 wire Direct segment to /bots/ placeholder and rename label to Роботы

This commit is contained in:
v.lagerev 2026-05-01 14:42:00 +03:00
parent d4eddf95d7
commit f59944bb4e
9 changed files with 84 additions and 23 deletions

View file

@ -370,7 +370,7 @@
"start_first_chat": "Start a chat", "start_first_chat": "Start a chat",
"segment_dm": "DM", "segment_dm": "DM",
"segment_channels": "Channels", "segment_channels": "Channels",
"segment_bots": "Bots", "segment_bots": "Robots",
"segment_coming_soon": "Coming soon", "segment_coming_soon": "Coming soon",
"self_row_label": "You", "self_row_label": "You",
"self_row_preview": "Settings & profile", "self_row_preview": "Settings & profile",
@ -917,5 +917,8 @@
"invite_body_no_room": "{{inviter}} invited you to a room", "invite_body_no_room": "{{inviter}} invited you to a room",
"invite_body_no_inviter": "Invited you to {{roomName}}", "invite_body_no_inviter": "Invited you to {{roomName}}",
"invite_body_generic": "New invitation" "invite_body_generic": "New invitation"
},
"Bots": {
"title": "Robots"
} }
} }

View file

@ -370,7 +370,7 @@
"start_first_chat": "Начать чат", "start_first_chat": "Начать чат",
"segment_dm": "Личные", "segment_dm": "Личные",
"segment_channels": "Каналы", "segment_channels": "Каналы",
"segment_bots": "Боты", "segment_bots": "Роботы",
"segment_coming_soon": "Скоро", "segment_coming_soon": "Скоро",
"self_row_label": "Я", "self_row_label": "Я",
"self_row_preview": "Настройки и профиль", "self_row_preview": "Настройки и профиль",
@ -921,5 +921,8 @@
"invite_body_no_room": "{{inviter}} приглашает вас в комнату", "invite_body_no_room": "{{inviter}} приглашает вас в комнату",
"invite_body_no_inviter": "Приглашение в {{roomName}}", "invite_body_no_inviter": "Приглашение в {{roomName}}",
"invite_body_generic": "Новое приглашение" "invite_body_generic": "Новое приглашение"
},
"Bots": {
"title": "Роботы"
} }
} }

View file

@ -1,7 +1,7 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useMatch } from 'react-router-dom'; import { useMatch } from 'react-router-dom';
import { ScreenSize, useScreenSizeContext } from '../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../hooks/useScreenSize';
import { DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from './paths'; import { BOTS_PATH, DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from './paths';
type MobileFriendlyClientNavProps = { type MobileFriendlyClientNavProps = {
children: ReactNode; children: ReactNode;
@ -13,10 +13,11 @@ export function MobileFriendlyClientNav({ children }: MobileFriendlyClientNavPro
const spaceMatch = useMatch({ path: SPACE_PATH, caseSensitive: true, end: true }); const spaceMatch = useMatch({ path: SPACE_PATH, caseSensitive: true, end: true });
const exploreMatch = useMatch({ path: EXPLORE_PATH, caseSensitive: true, end: true }); const exploreMatch = useMatch({ path: EXPLORE_PATH, caseSensitive: true, end: true });
const inboxMatch = useMatch({ path: INBOX_PATH, caseSensitive: true, end: true }); const inboxMatch = useMatch({ path: INBOX_PATH, caseSensitive: true, end: true });
const botsMatch = useMatch({ path: BOTS_PATH, caseSensitive: true, end: true });
if ( if (
screenSize === ScreenSize.Mobile && screenSize === ScreenSize.Mobile &&
!(homeMatch || directMatch || spaceMatch || exploreMatch || inboxMatch) !(homeMatch || directMatch || spaceMatch || exploreMatch || inboxMatch || botsMatch)
) { ) {
return null; return null;
} }

View file

@ -13,6 +13,7 @@ import {
import { ClientConfig } from '../hooks/useClientConfig'; import { ClientConfig } from '../hooks/useClientConfig';
import { AuthLayout, Login, Register, ResetPassword } from './auth'; import { AuthLayout, Login, Register, ResetPassword } from './auth';
import { import {
BOTS_PATH,
DIRECT_PATH, DIRECT_PATH,
EXPLORE_PATH, EXPLORE_PATH,
HOME_PATH, HOME_PATH,
@ -50,6 +51,7 @@ import { getMxIdServer, isUserId } from '../utils/matrix';
import { ClientBindAtoms, ClientLayout, ClientRoot } from './client'; import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
import { HomeRouteRoomProvider } from './client/home'; import { HomeRouteRoomProvider } from './client/home';
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct'; import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
import { Bots } from './client/bots';
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space'; import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
import { Explore, FeaturedRooms, PublicRooms } from './client/explore'; import { Explore, FeaturedRooms, PublicRooms } from './client/explore';
import { Notifications, Inbox, Invites } from './client/inbox'; import { Notifications, Inbox, Invites } from './client/inbox';
@ -243,6 +245,24 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
} }
/> />
</Route> </Route>
{/* Bots reuses DirectStreamHeader segments. /bots/* is reserved before SPACE_PATH so deep URLs don't fall to /:spaceIdOrAlias/. Real bot list lands in M2. */}
<Route
path={BOTS_PATH}
element={
<PageRoot
nav={
<MobileFriendlyPageNav path={BOTS_PATH}>
<Bots />
</MobileFriendlyPageNav>
}
>
<Outlet />
</PageRoot>
}
>
{mobile ? null : <Route index element={<WelcomePage />} />}
</Route>
<Route path="/bots/*" element={<Navigate to={BOTS_PATH} replace />} />
<Route <Route
path={SPACE_PATH} path={SPACE_PATH}
element={ element={

View file

@ -0,0 +1,28 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, Icons, Text } from 'folds';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { NavEmptyCenter, NavEmptyLayout } from '../../../components/nav';
import { PageNav } from '../../../components/page';
import { DirectStreamHeader } from '../direct/DirectStreamHeader';
export function Bots() {
const { t } = useTranslation();
useNavToActivePathMapper('bots');
return (
<PageNav size="500">
<DirectStreamHeader />
<NavEmptyCenter>
<NavEmptyLayout
icon={<Icon size="600" src={Icons.Bulb} />}
title={
<Text size="H5" align="Center">
{t('Bots.title')}
</Text>
}
/>
</NavEmptyCenter>
</PageNav>
);
}

View file

@ -0,0 +1 @@
export * from './Bots';

View file

@ -1,7 +1,10 @@
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMatch, useNavigate } from 'react-router-dom';
import { Box, Text, Tooltip, TooltipProvider, color, toRem } from 'folds'; import { Box, Text, Tooltip, TooltipProvider, color, toRem } from 'folds';
import { PageNavHeader } from '../../../components/page'; import { PageNavHeader } from '../../../components/page';
import { BOTS_PATH, DIRECT_PATH } from '../../paths';
import { isNativePlatform } from '../../../utils/capacitor';
type SegmentProps = { type SegmentProps = {
active: boolean; active: boolean;
@ -39,12 +42,22 @@ const Segment = forwardRef<HTMLButtonElement, SegmentProps>(
export function DirectStreamHeader() { export function DirectStreamHeader() {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate();
const comingSoon = t('Direct.segment_coming_soon'); const comingSoon = t('Direct.segment_coming_soon');
const directMatch = useMatch({ path: DIRECT_PATH, caseSensitive: true, end: false });
const botsMatch = useMatch({ path: BOTS_PATH, caseSensitive: true, end: false });
const navOpts = { replace: isNativePlatform() };
return ( return (
<PageNavHeader> <PageNavHeader>
<Box alignItems="Center" grow="Yes" gap="100"> <Box alignItems="Center" grow="Yes" gap="100">
<Segment active label={t('Direct.segment_dm')} /> <Segment
active={!!directMatch}
label={t('Direct.segment_dm')}
onClick={() => navigate(DIRECT_PATH, navOpts)}
/>
<TooltipProvider <TooltipProvider
delay={400} delay={400}
position="Bottom" position="Bottom"
@ -63,24 +76,11 @@ export function DirectStreamHeader() {
/> />
)} )}
</TooltipProvider> </TooltipProvider>
<TooltipProvider <Segment
delay={400} active={!!botsMatch}
position="Bottom" label={t('Direct.segment_bots')}
tooltip={ onClick={() => navigate(BOTS_PATH, navOpts)}
<Tooltip> />
<Text size="T200">{comingSoon}</Text>
</Tooltip>
}
>
{(triggerRef) => (
<Segment
ref={triggerRef as React.RefCallback<HTMLButtonElement>}
active={false}
disabled
label={t('Direct.segment_bots')}
/>
)}
</TooltipProvider>
</Box> </Box>
</PageNavHeader> </PageNavHeader>
); );

View file

@ -1,5 +1,6 @@
import { generatePath, Path } from 'react-router-dom'; import { generatePath, Path } from 'react-router-dom';
import { import {
BOTS_PATH,
DIRECT_CREATE_PATH, DIRECT_CREATE_PATH,
DIRECT_PATH, DIRECT_PATH,
DIRECT_ROOM_PATH, DIRECT_ROOM_PATH,
@ -158,3 +159,5 @@ export const getCreatePath = (): string => CREATE_PATH;
export const getInboxPath = (): string => INBOX_PATH; export const getInboxPath = (): string => INBOX_PATH;
export const getInboxNotificationsPath = (): string => INBOX_NOTIFICATIONS_PATH; export const getInboxNotificationsPath = (): string => INBOX_NOTIFICATIONS_PATH;
export const getInboxInvitesPath = (): string => INBOX_INVITES_PATH; export const getInboxInvitesPath = (): string => INBOX_INVITES_PATH;
export const getBotsPath = (): string => BOTS_PATH;

View file

@ -85,6 +85,8 @@ export const CREATE_PATH = '/create';
export const USER_LINK_HOST = 'vojo.chat'; export const USER_LINK_HOST = 'vojo.chat';
export const USER_LINK_PATH = '/u/:userIdOrLocalPart'; export const USER_LINK_PATH = '/u/:userIdOrLocalPart';
export const BOTS_PATH = '/bots/';
export const _NOTIFICATIONS_PATH = 'notifications/'; export const _NOTIFICATIONS_PATH = 'notifications/';
export const _INVITES_PATH = 'invites/'; export const _INVITES_PATH = 'invites/';
export const INBOX_PATH = '/inbox/'; export const INBOX_PATH = '/inbox/';