import React from 'react'; import { Navigate, Outlet, Route, createBrowserRouter, createHashRouter, createRoutesFromElements, redirect, useParams, } from 'react-router-dom'; import { ClientConfig } from '../hooks/useClientConfig'; import { AuthLayout, Login, Register, ResetPassword } from './auth'; import { BOTS_PATH, CHANNELS_PATH, CHANNELS_ROOM_EVENT_PATH, CHANNELS_ROOM_PATH, CHANNELS_SPACE_PATH, CHANNELS_THREAD_PATH, DIRECT_PATH, EXPLORE_PATH, HOME_PATH, LOGIN_PATH, REGISTER_PATH, RESET_PASSWORD_PATH, SETTINGS_PATH, SPACE_PATH, _CREATE_PATH, _FEATURED_PATH, _JOIN_PATH, _LOBBY_PATH, _ROOM_PATH, _SEARCH_PATH, _SERVER_PATH, CREATE_PATH, USER_LINK_HOST, USER_LINK_PATH, DirectCreateSearchParams, } from './paths'; import { getAppPathFromHref, getDirectCreatePath, getExploreFeaturedPath, getHomePath, getLoginPath, getOriginBaseUrl, getSpaceLobbyPath, withSearchParam, } from './pathUtils'; import { getMxIdServer, isUserId } from '../utils/matrix'; import { ClientBindAtoms, ClientLayout, ClientRoot } from './client'; import { HomeRouteRoomProvider } from './client/home'; import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct'; import { BotExperienceHost, Bots } from './client/bots'; import { Channels, ChannelsRootNav, ChannelPickPlaceholder, ChannelsLanding, } from './client/channels'; import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space'; import { Explore, FeaturedRooms, PublicRooms } from './client/explore'; import { setAfterLoginRedirectPath } from './afterLoginRedirectPath'; import { Room } from '../features/room'; import { Lobby } from '../features/lobby'; import { WelcomePage } from './client/WelcomePage'; import { PageRoot } from '../components/page'; import { ScreenSize } from '../hooks/useScreenSize'; import { MobileFriendlyPageNav } from './MobileFriendly'; import { ClientInitStorageAtom } from './client/ClientInitStorageAtom'; import { ClientNonUIFeatures } from './client/ClientNonUIFeatures'; import { AuthRouteThemeManager, UnAuthRouteThemeManager } from './ThemeManager'; import { ReceiveSelfDeviceVerification } from '../components/DeviceVerification'; import { AutoRestoreBackupOnVerification } from '../components/BackupRestore'; import { RoomSettingsRenderer } from '../features/room-settings'; import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences'; import { SpaceSettingsRenderer } from '../features/space-settings'; import { CreateRoomModalRenderer } from '../features/create-room'; import { Create } from './client/create'; import { CreateSpaceModalRenderer } from '../features/create-space'; import { SearchModalRenderer } from '../features/search'; import { SettingsScreen } from '../features/settings'; import { getFallbackSession } from '../state/sessions'; import { CallEmbedProvider } from '../components/CallEmbedProvider'; import { useIncomingRtcNotifications } from '../hooks/useIncomingRtcNotifications'; import { useCallerAutoHangup } from '../hooks/useCallerAutoHangup'; import { usePendingCallActionConsumer } from '../hooks/usePendingCallActionConsumer'; import { HorseshoeContainer } from './HorseshoeContainer'; import { useAppUrlOpen } from '../hooks/useAppUrlOpen'; import { ChannelsModeProvider } from '../hooks/useChannelsMode'; function IncomingCallsFeature() { useIncomingRtcNotifications(); useCallerAutoHangup(); usePendingCallActionConsumer(); // Native CallStyle dismissal is owned by the Android ring registry: // VojoFirebaseMessagingService.removeIncomingRing (ownership-checked cancel) // fires on atom REMOVE via bridge, and MainActivity.onResume calls // cancelRenderedIncomingRings for the background→foreground handoff. // A JS-side dismiss hook here is redundant and risks a blind tag/id cancel // hitting a foreign ring in the same room slot. useAppUrlOpen(); return null; } // Deep-link entry for /u/. `` is either a bare localpart or a // full MXID; we normalize to an MXID using USER_LINK_HOST (the deep-link's own // host, NOT the logged-in user's homeserver — a user signed into matrix.org // opening vojo.chat/u/test3 still means @test3:vojo.chat) and forward to the // existing DirectCreate flow, which itself dedupes to any existing DM via // getDMRoomFor. function UserLinkRedirect() { const { userIdOrLocalPart } = useParams(); if (!userIdOrLocalPart) return ; const raw = decodeURIComponent(userIdOrLocalPart); const mxid = isUserId(raw) && getMxIdServer(raw) ? raw : `@${raw}:${USER_LINK_HOST}`; if (!isUserId(mxid)) return ; const params: DirectCreateSearchParams = { userId: mxid }; return ; } export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => { const { hashRouter } = clientConfig; const mobile = screenSize === ScreenSize.Mobile; const routes = createRoutesFromElements( { if (getFallbackSession()) return redirect(getHomePath()); const afterLoginPath = getAppPathFromHref(getOriginBaseUrl(), window.location.href); if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath); return redirect(getLoginPath()); }} /> { if (getFallbackSession()) { return redirect(getHomePath()); } return null; }} element={ <> } > } /> } /> } /> { const session = getFallbackSession(); if (!session) { const afterLoginPath = getAppPathFromHref( getOriginBaseUrl(hashRouter), window.location.href ); if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath); return redirect(getLoginPath()); } return null; }} element={ {/* SidebarNav (66px icon-rail) временно отключён — позже растащим его 5 кнопок (Settings, Search, Explore, Create, Unverified) по новым поверхностям интерфейса. Сам компонент жив: src/app/pages/client/SidebarNav.tsx + ./sidebar/*. См. docs/plans/redesign_overview.md → sidebar_cleanup. */} } > {/* Legacy /home/ tree — kept only as a redirect surface so cold-start push deep links and pre-P3c bookmarks resolve cleanly. The Home page itself is gone; HomeRouteRoomProvider redirects /home/{roomId}/ into /direct/{roomId}/ on mount. See plan §6.7 / §8 P3c. */} } /> } /> } /> } /> } /> } > } > {mobile ? null : } />} } /> } /> {/* Bots reuses DirectStreamHeader segments. /bots/* is reserved before SPACE_PATH so deep URLs don't fall to /:spaceIdOrAlias/. */} } > } > {mobile ? null : } />} } /> } /> {/* Channels segment. /channels/* is reserved before SPACE_PATH so the generic /:spaceIdOrAlias/ catch-all doesn't swallow the prefix. Phase 1 routes resolve to a stubbed center pane; Phase 3 + Phase 4 replace the left list and center timeline respectively. */} } > } > } /> } > } > {mobile ? null : } />} } > {/* Thread drawer URL — same Room element renders, drawer opens by reading `:rootId` via useParams. The SpaceRouteRoomProvider lives on the parent route and stays mounted across the room↔thread URL flip. */} {/* Event-anchored URL — search/inbox/mention/push permalinks. RR6 merges child params into the parent's useParams so Room.tsx reads `eventId` without re-routing. */} } > } > {mobile ? null : ( { const { spaceIdOrAlias } = params; if (spaceIdOrAlias) { return redirect(getSpaceLobbyPath(spaceIdOrAlias)); } return null; }} element={} /> )} } /> } /> } /> } > } > {mobile ? null : ( redirect(getExploreFeaturedPath())} element={} /> )} } /> } /> } /> {/* /settings shares the DIRECT_PATH shell — left page-nav stays the DM list, the right pane swaps the chat outlet for the Settings UI. The horseshoe rounded TL/BL on the right pane (commits 363bd9d / 74d32eb) inherits from PageRoot for free. On mobile MobileFriendlyPageNav hides the DM list since /settings/ ≠ DIRECT_PATH; SettingsScreen renders the bottom-up horseshoe sheet over the empty outlet area. */} } > } /> } /> {/* Legacy /inbox/ tree — invites moved inline into the Direct list, the Notifications aggregator was removed. Keep the route as a redirect so old push deep-links and bookmarks resolve cleanly. */} } /> Page not found

} />
); if (hashRouter?.enabled) { return createHashRouter(routes, { basename: hashRouter.basename }); } return createBrowserRouter(routes, { basename: import.meta.env.BASE_URL, }); };