群解散事件未处理、错误码翻译缺失、恢复登录页误导性提示 - 好友申请首次必失败30000(P0):AddFriendModal把手机号搜索单独标记为 source:"phone"上报,但后端serveIMFriendRequestCreate的source校验只认 search/qr/group,命中default分支返回"invalid friend request source"。 改为手机号搜索也统一上报source:"search"(后端本就不区分具体搜索渠道), 用两个全新、此前无任何关系的测试账号验证首次申请200成功 - 解散群聊需要短信二次验证但完整流程从未实现(P0):新增 SecurityChallengeModal通用二次验证弹层(POST /security/challenges→ 验证码→verify→action_token)+ requestActionToken()(stores/ securityChallenge.ts,与requestConfirm同一模式),接入解散群聊和转让 群主(后者此前也因缺X-Action-Token必然404/400)。同时发现并修复 imDelete对无body请求不显式传空对象体,导致携带Content-Type: application/json但请求体真正为空时后端JSON解码失败返回400——实测确认 两个问题都解决后DELETE返回200 - 群聊被解散但当前正查看该群的用户界面不会跳转(P1):新增 topics.ts的subscribeTopicGone()监听SDK自身的onDeleteTopic回调, ConversationView据此在当前会话被解散时弹出提示并跳转回消息列表 - P2P会话发起方标题栏显示原始topic id、对方明明在线却显示离线:确认 为上一轮已修复的isCommType()根因问题的同一症状,本轮实测(新建从未 聊过天的账号对)已不再复现;群内消息发送者显示原始uid的关联描述同样 实测未复现 - 好友关系冲突(已是好友/申请正在处理中)报错英文原文透出:翻译表补充 FRIEND_REQUEST_CONFLICT - 深链/刷新页面时的"改为重新登录"链接此前在任何恢复登录过程中都会 立即显示(不管是否超时),造成短暂白屏时的误导性提示:改为仅在真正 超时后才显示该链接,与其上方的提示文案保持一致的显示条件
114 lines
5.5 KiB
TypeScript
114 lines
5.5 KiB
TypeScript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { Navigate, createBrowserRouter, RouterProvider } from 'react-router-dom';
|
|
|
|
import { AppShell } from '@/app/AppShell';
|
|
import { ConfirmDialogRoot } from '@/app/ConfirmDialogRoot';
|
|
import { GlobalToastRoot } from '@/app/GlobalToastRoot';
|
|
import { KickedNoticeRoot } from '@/app/KickedNoticeRoot';
|
|
import { RequireAuth } from '@/app/RequireAuth';
|
|
import { SecurityChallengeModal } from '@/app/SecurityChallengeModal';
|
|
import { EnterpriseRegisterPage } from '@/features/auth/EnterpriseRegisterPage';
|
|
import { LoginPage } from '@/features/auth/LoginPage';
|
|
import { ResetPasswordPage } from '@/features/auth/ResetPasswordPage';
|
|
import { TenantEntryPage } from '@/features/auth/TenantEntryPage';
|
|
import { TenantFeedbackPage } from '@/features/auth/TenantFeedbackPage';
|
|
import { CallHistoryView } from '@/features/calls/CallHistoryView';
|
|
import { BlocklistView } from '@/features/contacts/BlocklistView';
|
|
import { ContactsLayout } from '@/features/contacts/ContactsLayout';
|
|
import { EmptyContactsMain } from '@/features/contacts/EmptyContactsMain';
|
|
import { FriendProfileView } from '@/features/contacts/FriendProfileView';
|
|
import { GroupListView } from '@/features/contacts/GroupListView';
|
|
import { NewFriendsView } from '@/features/contacts/NewFriendsView';
|
|
import { FavoritesLayout } from '@/features/favorites/FavoritesLayout';
|
|
import { ConversationView } from '@/features/messages/ConversationView';
|
|
import { EmptyMain } from '@/features/messages/EmptyMain';
|
|
import { MessagesLayout } from '@/features/messages/MessagesLayout';
|
|
import { GeneralSettingsPage } from '@/features/settings/GeneralSettingsPage';
|
|
import { NotificationsSettingsPage } from '@/features/settings/NotificationsSettingsPage';
|
|
import { PrivacySettingsPage } from '@/features/settings/PrivacySettingsPage';
|
|
import { ProfileSettingsPage } from '@/features/settings/ProfileSettingsPage';
|
|
import { SecuritySettingsPage } from '@/features/settings/SecuritySettingsPage';
|
|
import { SettingsHome } from '@/features/settings/SettingsHome';
|
|
import { SettingsLayout } from '@/features/settings/SettingsLayout';
|
|
import { SystemMessagesView } from '@/features/system/SystemMessagesView';
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
const router = createBrowserRouter([
|
|
{ path: 'tenant', element: <TenantEntryPage /> },
|
|
{ path: 'login', element: <LoginPage /> },
|
|
{ path: 'reset-password', element: <ResetPasswordPage /> },
|
|
{ path: 'enterprise-register', element: <EnterpriseRegisterPage /> },
|
|
{ path: 'feedback', element: <TenantFeedbackPage /> },
|
|
{
|
|
element: <RequireAuth />,
|
|
children: [
|
|
{
|
|
element: <AppShell />,
|
|
children: [
|
|
{ index: true, element: <Navigate to="/messages" replace /> },
|
|
{
|
|
path: 'messages',
|
|
element: <MessagesLayout />,
|
|
children: [
|
|
{ index: true, element: <EmptyMain /> },
|
|
{ path: 'system', element: <SystemMessagesView /> },
|
|
{ path: ':topicId', element: <ConversationView /> },
|
|
],
|
|
},
|
|
{
|
|
path: 'contacts',
|
|
element: <ContactsLayout />,
|
|
children: [
|
|
{ index: true, element: <EmptyContactsMain /> },
|
|
{ path: 'requests', element: <NewFriendsView /> },
|
|
{ path: 'groups', element: <GroupListView /> },
|
|
{ path: 'blocklist', element: <BlocklistView /> },
|
|
{ path: ':userId', element: <FriendProfileView /> },
|
|
],
|
|
},
|
|
{ path: 'favorites', element: <FavoritesLayout /> },
|
|
// README §9.2's dedicated file-assistant browser (categories, storage quota, upload
|
|
// progress) has NO backing REST API at all (confirmed via a research pass -- the design
|
|
// spec's own state-management notes admit reuse of existing APIs, i.e. reading the `slf`
|
|
// topic's messages, same as any other conversation) -- redirecting into the real
|
|
// conversation on that topic is honest; a fake quota card with invented numbers isn't.
|
|
{ path: 'files', element: <Navigate to="/messages/slf" replace /> },
|
|
{ path: 'calls', element: <CallHistoryView /> },
|
|
{
|
|
path: 'settings',
|
|
element: <SettingsLayout />,
|
|
children: [
|
|
{ index: true, element: <SettingsHome /> },
|
|
{ path: 'profile', element: <ProfileSettingsPage /> },
|
|
{ path: 'security', element: <SecuritySettingsPage /> },
|
|
{ path: 'notifications', element: <NotificationsSettingsPage /> },
|
|
{ path: 'privacy', element: <PrivacySettingsPage /> },
|
|
{ path: 'general', element: <GeneralSettingsPage /> },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
// A stale bookmark/link to a route renamed during this redesign should land somewhere the app
|
|
// itself resolves (tenant/login/messages, per RequireAuth's own gating), not React Router's
|
|
// generic unstyled default error boundary.
|
|
{ path: '*', element: <Navigate to="/" replace /> },
|
|
]);
|
|
|
|
export default function App() {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<RouterProvider router={router} />
|
|
{/* Mounted above the router (not inside AppShell) -- window.confirm()/window.alert()
|
|
replacements need to work on unauthenticated pages too (e.g. TenantEntryPage's "switch
|
|
tenant" confirmation), not just the logged-in app tree. */}
|
|
<ConfirmDialogRoot />
|
|
<GlobalToastRoot />
|
|
<KickedNoticeRoot />
|
|
<SecurityChallengeModal />
|
|
</QueryClientProvider>
|
|
);
|
|
}
|