diff --git a/apps/api/package.json b/apps/api/package.json index 0e9d645..2b77285 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -38,13 +38,13 @@ "devDependencies": { "@types/bcryptjs": "^2.4.6", "@types/lodash": "^4.17.24", - "@types/node": "^22.0.0", + "@types/node": "^24.0.0", "@types/nodemailer": "^8.0.0", "@types/react": "19.2.2", "@types/react-dom": "19.2.3", "@types/uuid": "^10.0.0", "tsx": "^4.19.0", - "typescript": "^5.7.0", + "typescript": "^7.0.0", "vite": "catalog:", "vite-plus": "catalog:" } diff --git a/apps/api/src/lib/db.ts b/apps/api/src/lib/db.ts index 8c5d9ba..6c998fa 100644 --- a/apps/api/src/lib/db.ts +++ b/apps/api/src/lib/db.ts @@ -21,7 +21,7 @@ import { boolEq, boolValue } from "./db-compat"; import { deleteReplyWithStore, type ReplyDeletionStore } from "./reply-deletion"; import { createReplyWithStore, type ReplyCreationStore } from "./reply-creation"; -let dbInstance: DB | null = null; +let dbInstance: DB = null; const INTERNAL_TABS = ["dev", "test"]; function getDb(): DB { diff --git a/apps/api/src/lib/moderation-scan.ts b/apps/api/src/lib/moderation-scan.ts index c3388df..644c9f4 100644 --- a/apps/api/src/lib/moderation-scan.ts +++ b/apps/api/src/lib/moderation-scan.ts @@ -35,8 +35,9 @@ export function scanDefaults() { function decodeJsonArray(value: unknown): number[] | null { if (!value) return null; if (Array.isArray(value)) return value.map(Number).filter((v) => v > 0); + if (typeof value !== "string") return null; try { - const parsed = JSON.parse(String(value)); + const parsed = JSON.parse(value); return Array.isArray(parsed) ? parsed.map(Number).filter((v) => v > 0) : null; } catch { return null; diff --git a/apps/api/src/middleware/auth.ts b/apps/api/src/middleware/auth.ts index e4b37d3..8542ff4 100644 --- a/apps/api/src/middleware/auth.ts +++ b/apps/api/src/middleware/auth.ts @@ -4,7 +4,7 @@ import { roleQueries, userQueries } from "../lib/db"; import { resolveUserAccess } from "../lib/user-access"; export interface AuthVars { - user: Awaited> | null; + user: Awaited>; isLogin: boolean; isAdmin: boolean; isMod: boolean; diff --git a/apps/api/src/routes/auth.ts b/apps/api/src/routes/auth.ts index 7ee0dab..741d66c 100644 --- a/apps/api/src/routes/auth.ts +++ b/apps/api/src/routes/auth.ts @@ -572,7 +572,7 @@ auth.openapi(githubCreateRoute, async (c) => { if (!profile) return c.json({ success: false as const, error_msg: "GitHub 登录状态已过期,请重新授权" }, 401); const body = c.req.valid("json"); - let user: Awaited> | null = null; + let user: Awaited> = null; if (body.isnew) { const loginname = profile.login.toLowerCase(); if (await userQueries.getByLoginName(loginname)) @@ -706,7 +706,7 @@ auth.openapi(githubUnbindRoute, async (c) => { let result: Awaited>; try { result = await executeGithubUnbind(user, password, { - clearGithubInfo: userQueries.clearGithubInfo, + clearGithubInfo: (userId, githubId) => userQueries.clearGithubInfo(userId, githubId), revokeToken: revokeGithubToken, verifyPassword: bcryptjs.compare, }); @@ -999,8 +999,8 @@ auth.post("/upload/image", async (c) => { return c.json({ success: false, error_msg: "只支持 png/jpeg/gif/webp/svg 图片上传" }, 422); const maxSize = Number(process.env.OSS_UPLOAD_MAX_BYTES || 5 * 1024 * 1024); if (file.size > maxSize) return c.json({ success: false, error_msg: "图片不能超过 5MB" }, 413); - const purpose = - typeof formData?.get("purpose") === "string" ? String(formData.get("purpose")) : null; + const purposeValue = formData?.get("purpose"); + const purpose = typeof purposeValue === "string" ? purposeValue : null; const filename = `${uploadPrefix(purpose)}/${uuidv4()}${extensionForContentType(file.type)}`; await createOssClient().put(filename, Buffer.from(await file.arrayBuffer()), { headers: { "Content-Type": file.type }, diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 89eb55e..ec38d5e 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "jsx": "react-jsx", + "rootDir": "../..", "outDir": "./dist", "types": ["node"], "noImplicitAny": false, diff --git a/apps/web/app/components/CommandPalette.tsx b/apps/web/app/components/CommandPalette.tsx index 48abefd..1027218 100644 --- a/apps/web/app/components/CommandPalette.tsx +++ b/apps/web/app/components/CommandPalette.tsx @@ -72,12 +72,12 @@ export function CommandPalette({ const value = query.trim(); if (!value) return; onOpenChange(false); - navigate(`/search?q=${encodeURIComponent(value)}`); + void navigate(`/search?q=${encodeURIComponent(value)}`); } function go(to: string) { onOpenChange(false); - navigate(to); + void navigate(to); } return ( diff --git a/apps/web/app/components/ThemeToggle.tsx b/apps/web/app/components/ThemeToggle.tsx index 4be049c..d1f766a 100644 --- a/apps/web/app/components/ThemeToggle.tsx +++ b/apps/web/app/components/ThemeToggle.tsx @@ -9,7 +9,7 @@ export function ThemeToggle() { const applyToDocument = useThemeStore((s) => s.applyToDocument); useEffect(() => { - Promise.resolve(useThemeStore.persist.rehydrate()).then(() => { + void Promise.resolve(useThemeStore.persist.rehydrate()).then(() => { useThemeStore.getState().applyToDocument(); }); }, [applyToDocument]); diff --git a/apps/web/app/lib/stores/auth-store.ts b/apps/web/app/lib/stores/auth-store.ts index 6fd11ce..15e63af 100644 --- a/apps/web/app/lib/stores/auth-store.ts +++ b/apps/web/app/lib/stores/auth-store.ts @@ -31,7 +31,7 @@ export const useAuthStore = create()((set, get) => ({ if (get().hydrated) return; set({ user, hydrated: true }); if (user) { - get().fetchUnread(); + void get().fetchUnread(); } }, fetchUnread: async () => { diff --git a/apps/web/app/routes/_index.tsx b/apps/web/app/routes/_index.tsx index 44a5bee..47521cc 100644 --- a/apps/web/app/routes/_index.tsx +++ b/apps/web/app/routes/_index.tsx @@ -101,7 +101,7 @@ export default function Index({ loaderData }: Route.ComponentProps) { onValueChange={(value) => { const params = new URLSearchParams(); if (value !== "all") params.set("tab", value); - navigate(params.size ? `/?${params.toString()}` : "/"); + void navigate(params.size ? `/?${params.toString()}` : "/"); }} > diff --git a/apps/web/app/routes/admin/bans.tsx b/apps/web/app/routes/admin/bans.tsx index c6070fa..2e64d41 100644 --- a/apps/web/app/routes/admin/bans.tsx +++ b/apps/web/app/routes/admin/bans.tsx @@ -156,8 +156,8 @@ export default function AdminBans({ loaderData }: any) { currentItemCount: bannedUsers.length, removedCount: 1, }); - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(result.error_msg || `${userActionLabel}失败`); } @@ -195,8 +195,8 @@ export default function AdminBans({ loaderData }: any) { currentItemCount: bannedUsers.length, removedCount: result.processed || 0, }); - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(result.error_msg || `批量${userActionLabel}失败`); } @@ -218,7 +218,7 @@ export default function AdminBans({ loaderData }: any) { setIp(""); setReason(""); setAddIpConfirmOpen(false); - revalidate(); + void revalidate(); } else { toast.error(result.error_msg || "添加 IP 规则失败"); } @@ -243,8 +243,8 @@ export default function AdminBans({ loaderData }: any) { currentItemCount: bannedIps.length, removedCount: 1, }); - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(res.error_msg || "移除失败"); } diff --git a/apps/web/app/routes/admin/keywords.tsx b/apps/web/app/routes/admin/keywords.tsx index 0d5e8cf..a915e53 100644 --- a/apps/web/app/routes/admin/keywords.tsx +++ b/apps/web/app/routes/admin/keywords.tsx @@ -63,7 +63,7 @@ export default function AdminKeywords({ loaderData }: any) { if (res.success) { toast.success("已添加"); setNewWord(""); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "添加失败"); } @@ -93,8 +93,8 @@ export default function AdminKeywords({ loaderData }: any) { currentItemCount: keywords.length, removedCount: 1, }); - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(res.error_msg || "删除失败"); } @@ -120,7 +120,7 @@ export default function AdminKeywords({ loaderData }: any) { toast.success(`已导入 ${result.count} 条`); setBulkText(""); setShowBulk(false); - revalidate(); + void revalidate(); } else { toast.error(result.error_msg || "导入失败"); } diff --git a/apps/web/app/routes/admin/mod.tsx b/apps/web/app/routes/admin/mod.tsx index 4309c17..a2a0dd4 100644 --- a/apps/web/app/routes/admin/mod.tsx +++ b/apps/web/app/routes/admin/mod.tsx @@ -112,7 +112,7 @@ export default function AdminMod({ loaderData }: any) { if (res.success) { toast.success("操作成功"); setDeleteTarget(null); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "操作失败"); } @@ -135,7 +135,7 @@ export default function AdminMod({ loaderData }: any) { toast.success(`已处理 ${res.handled || selected.length} 条`); setSelected([]); setDeleteTarget(null); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "批量操作失败"); } @@ -158,7 +158,7 @@ export default function AdminMod({ loaderData }: any) { toast.success(`已确认删除 ${res.handled || 0} 条命中内容`); setConfirmJobId(null); setSelected([]); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "任务批量确认删除失败"); } @@ -181,7 +181,7 @@ export default function AdminMod({ loaderData }: any) { onSuccess: (res) => { if (res.success) { toast.success("扫描任务已创建"); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "创建任务失败"); } @@ -208,7 +208,7 @@ export default function AdminMod({ loaderData }: any) { : "任务已更新", ); if (result.action === "cancel") setCancelJobId(null); - revalidate(); + void revalidate(); } else { toast.error(result.error_msg || "更新任务失败"); } diff --git a/apps/web/app/routes/admin/reports.tsx b/apps/web/app/routes/admin/reports.tsx index 1e782aa..9b62aad 100644 --- a/apps/web/app/routes/admin/reports.tsx +++ b/apps/web/app/routes/admin/reports.tsx @@ -23,6 +23,20 @@ import { ItemTitle, } from "~/components/ui/item"; +type ReportRow = { + id: number; + type: string; + description: string | null; + status: string; + reporter_count: number; + target_type: string; + target_id: string; + topic_id: number | null; + topic_title: string; + target_summary: string; + create_at: string; +}; + export function meta() { return [{ title: "举报队列 · CNode Admin" }]; } @@ -46,7 +60,7 @@ export default function AdminReports({ loaderData }: any) { const { revalidate } = useRevalidator(); const location = useLocation(); const navigate = useNavigate(); - const [confirmTarget, setConfirmTarget] = useState(null); + const [confirmTarget, setConfirmTarget] = useState(null); const confirmTriggerRef = useRef(null); const { run: handleAction, pending: handling } = useAsyncAction( @@ -69,8 +83,8 @@ export default function AdminReports({ loaderData }: any) { currentItemCount: reports.length, removedCount: 1, }); - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(result.error_msg || "操作失败"); } diff --git a/apps/web/app/routes/admin/tabs.tsx b/apps/web/app/routes/admin/tabs.tsx index 99e9570..e21b11b 100644 --- a/apps/web/app/routes/admin/tabs.tsx +++ b/apps/web/app/routes/admin/tabs.tsx @@ -71,7 +71,7 @@ export default function AdminTabs({ loaderData }: { loaderData: any }) { ); if (res.success) { toast.success("已保存"); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "保存失败"); } diff --git a/apps/web/app/routes/admin/topics.tsx b/apps/web/app/routes/admin/topics.tsx index 16a8a69..cc3e0a7 100644 --- a/apps/web/app/routes/admin/topics.tsx +++ b/apps/web/app/routes/admin/topics.tsx @@ -158,8 +158,8 @@ export default function AdminTopics({ loaderData }: any) { removedCount: res.ids.length, }) : null; - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(res.error_msg || "操作失败"); } @@ -186,8 +186,8 @@ export default function AdminTopics({ loaderData }: any) { currentItemCount: topics.length, removedCount: res.deleted || permanentDeleteIds.length, }); - if (fallback) navigate(fallback, { replace: true }); - else revalidate(); + if (fallback) void navigate(fallback, { replace: true }); + else void revalidate(); } else { toast.error(res.error_msg || "永久删除失败"); } diff --git a/apps/web/app/routes/admin/users.tsx b/apps/web/app/routes/admin/users.tsx index 71bb112..2b46ba6 100644 --- a/apps/web/app/routes/admin/users.tsx +++ b/apps/web/app/routes/admin/users.tsx @@ -107,7 +107,7 @@ export default function AdminUsers({ loaderData }: any) { if (res.success) { toast.success("用户治理状态已更新"); setGovernanceTarget(null); - revalidate(); + void revalidate(); } else toast.error(res.error_msg || "操作失败"); }, }, @@ -131,7 +131,7 @@ export default function AdminUsers({ loaderData }: any) { if (res.success) { toast.success("用户角色已更新"); setRoleTarget(null); - revalidate(); + void revalidate(); } else toast.error(res.error_msg || "角色操作失败"); }, }, @@ -148,7 +148,7 @@ export default function AdminUsers({ loaderData }: any) { if (res.success) { toast.success("已删除该用户所有发言"); setDeleteAllTarget(null); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "删除失败"); } diff --git a/apps/web/app/routes/admin/zones.tsx b/apps/web/app/routes/admin/zones.tsx index b0d26fe..da00d15 100644 --- a/apps/web/app/routes/admin/zones.tsx +++ b/apps/web/app/routes/admin/zones.tsx @@ -74,7 +74,7 @@ export default function AdminZones({ loaderData }: { loaderData: any }) { ); if (res.success) { toast.success("已保存"); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "保存失败"); } diff --git a/apps/web/app/routes/auth.github.new.tsx b/apps/web/app/routes/auth.github.new.tsx index 20fb530..40430b8 100644 --- a/apps/web/app/routes/auth.github.new.tsx +++ b/apps/web/app/routes/auth.github.new.tsx @@ -48,7 +48,7 @@ export default function GithubNew({ loaderData }: Route.ComponentProps) { onSuccess: (res) => { if (res.success) { toast.success("GitHub 登录成功"); - navigate("/"); + void navigate("/"); } else { toast.error(res.error_msg || "GitHub 登录失败"); } diff --git a/apps/web/app/routes/my.messages.tsx b/apps/web/app/routes/my.messages.tsx index cc1b0e4..c992771 100644 --- a/apps/web/app/routes/my.messages.tsx +++ b/apps/web/app/routes/my.messages.tsx @@ -55,7 +55,7 @@ export default function Messages({ loaderData }: Route.ComponentProps) { useEffect(() => { setReadMsgs(initialRead || []); setUnreadMsgs(initialUnread || []); - fetchUnread(); + void fetchUnread(); }, [initialRead, initialUnread, fetchUnread]); const { run: markOneRead, pending: markingOne } = useAsyncAction( @@ -74,8 +74,8 @@ export default function Messages({ loaderData }: Route.ComponentProps) { if (msg) setReadMsgs((items) => [{ ...msg, has_read: true }, ...items]); setUnreadCount(Math.max(0, unreadMsgs.length - 1)); toast.success("已标记已读"); - fetchUnread(); - revalidate(); + void fetchUnread(); + void revalidate(); }, }, ); @@ -97,8 +97,8 @@ export default function Messages({ loaderData }: Route.ComponentProps) { setUnreadMsgs([]); setUnreadCount(0); toast.success("已全部标记已读"); - fetchUnread(); - revalidate(); + void fetchUnread(); + void revalidate(); }, }, ); diff --git a/apps/web/app/routes/reply.$id.edit.tsx b/apps/web/app/routes/reply.$id.edit.tsx index 317b9b2..b79af73 100644 --- a/apps/web/app/routes/reply.$id.edit.tsx +++ b/apps/web/app/routes/reply.$id.edit.tsx @@ -32,7 +32,8 @@ export default function ReplyEdit({ loaderData }: Route.ComponentProps) { const navigate = useNavigate(); const { revalidate } = useRevalidator(); const [content, setContent] = useState(reply?.content || ""); - const { blocker, allowNavigation } = useUnsavedChanges(content !== (reply?.content || "")); + const unsavedChanges = useUnsavedChanges(content !== (reply?.content || "")); + const { blocker } = unsavedChanges; const { run: submitReply, pending: saving } = useAsyncAction( async () => { @@ -45,9 +46,9 @@ export default function ReplyEdit({ loaderData }: Route.ComponentProps) { onSuccess: (res) => { if (res.success) { toast.success("已保存"); - allowNavigation(); - revalidate(); - navigate(-1); + unsavedChanges.allowNavigation(); + void revalidate(); + void navigate(-1); } else { toast.error(res.error_msg || "保存失败"); } diff --git a/apps/web/app/routes/setting.tsx b/apps/web/app/routes/setting.tsx index 3c32027..e63acce 100644 --- a/apps/web/app/routes/setting.tsx +++ b/apps/web/app/routes/setting.tsx @@ -138,7 +138,7 @@ export default function Setting({ loaderData }: Route.ComponentProps) { } toast.success("GitHub 已解除绑定"); handleUnbindOpenChange(false); - revalidator.revalidate(); + void revalidator.revalidate(); }; const onProfileSubmit = async (values: ProfileValues) => { diff --git a/apps/web/app/routes/signin.tsx b/apps/web/app/routes/signin.tsx index ae9f874..6c3d8d2 100644 --- a/apps/web/app/routes/signin.tsx +++ b/apps/web/app/routes/signin.tsx @@ -57,7 +57,7 @@ export default function Signin() { onSuccess: (res) => { if (res.success) { toast.success("登录成功"); - navigate("/"); + void navigate("/"); } else { toast.error(res.error_msg || "登录失败"); } diff --git a/apps/web/app/routes/topic.$tid.edit.tsx b/apps/web/app/routes/topic.$tid.edit.tsx index 930c08a..6fb75de 100644 --- a/apps/web/app/routes/topic.$tid.edit.tsx +++ b/apps/web/app/routes/topic.$tid.edit.tsx @@ -61,7 +61,7 @@ export default function TopicEdit() { const navigate = useNavigate(); useEffect(() => { - apiFetch<{ success: boolean; data: any }>(`/api/v1/topic/${tid}?mdrender=false`) + void apiFetch<{ success: boolean; data: any }>(`/api/v1/topic/${tid}?mdrender=false`) .then((res) => { if (res.success) { const nextTitle = res.data.title || ""; @@ -100,7 +100,8 @@ export default function TopicEdit() { const isDirty = !loading && initialRef.current !== JSON.stringify({ title, tab, content, jobMeta }); - const { blocker, allowNavigation } = useUnsavedChanges(isDirty); + const unsavedChanges = useUnsavedChanges(isDirty); + const { blocker } = unsavedChanges; const { run: submitTopic, pending: saving } = useAsyncAction( async () => { @@ -128,8 +129,8 @@ export default function TopicEdit() { onSuccess: (res) => { if (res.success) { toast.success("已保存"); - allowNavigation(); - navigate(`/topic/${tid}`); + unsavedChanges.allowNavigation(); + void navigate(`/topic/${tid}`); } else { toast.error(res.error_msg || "保存失败"); } diff --git a/apps/web/app/routes/topic.$tid.tsx b/apps/web/app/routes/topic.$tid.tsx index 1b8618a..99a4534 100644 --- a/apps/web/app/routes/topic.$tid.tsx +++ b/apps/web/app/routes/topic.$tid.tsx @@ -56,6 +56,7 @@ import { useAsyncAction } from "~/hooks/use-async-action"; import { UserIdentityBadges } from "~/components/UserIdentityBadges"; import { externalUrlLabel, githubProfileUrl, safeExternalUrl } from "~/lib/public-profile"; import { getTopicActionPresentation } from "~/lib/topic-action-presentation"; +import type { TopicReplyDTO } from "~/lib/api-types"; import { Empty, EmptyContent, @@ -472,7 +473,7 @@ export function TopicActions({ topic, currentUser }: { topic: any; currentUser: if (res.skipped) return; if (res.success) { toast.success(topic.is_collect ? "已取消收藏" : "已收藏话题"); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || (topic.is_collect ? "取消收藏失败" : "收藏失败")); } @@ -516,7 +517,7 @@ export function TopicActions({ topic, currentUser }: { topic: any; currentUser: if (res.success) { toast.success(res.message || `${res.actionLabel}成功`); setDeleteOpen(false); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || `${res.actionLabel}失败`); } @@ -659,7 +660,7 @@ function ReplySection({ topicId: string; currentUser: any; }) { - const [targetReply, setTargetReply] = useState(null); + const [targetReply, setTargetReply] = useState(null); const [content, setContent] = useState(""); const formRef = useRef(null); const { revalidate } = useRevalidator(); @@ -700,7 +701,7 @@ function ReplySection({ toast.success("回复成功"); setContent(""); setTargetReply(null); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "回复失败"); } @@ -835,7 +836,7 @@ function ReplyItem({ if (res.skipped) return; if (res.success) { toast.success(res.action === "down" ? "已取消点赞" : "已点赞"); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "点赞失败"); } @@ -860,7 +861,7 @@ function ReplyItem({ if (res.success) { toast.success("回复已删除"); setDeleteOpen(false); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "删除失败"); } diff --git a/apps/web/app/routes/topic.create.tsx b/apps/web/app/routes/topic.create.tsx index bf4c528..a16047a 100644 --- a/apps/web/app/routes/topic.create.tsx +++ b/apps/web/app/routes/topic.create.tsx @@ -73,7 +73,8 @@ export default function TopicCreate({ loaderData }: Route.ComponentProps) { (jobMeta.experience ?? "") !== "" || (jobMeta.tech_tags?.length ?? 0) > 0 || jobMeta.contact !== ""; - const { blocker, allowNavigation } = useUnsavedChanges(isDirty); + const unsavedChanges = useUnsavedChanges(isDirty); + const { blocker } = unsavedChanges; const { run: submitTopic, pending: saving } = useAsyncAction( async () => { @@ -109,8 +110,8 @@ export default function TopicCreate({ loaderData }: Route.ComponentProps) { onSuccess: (res) => { if (res.success) { toast.success("发布成功"); - allowNavigation(); - navigate(`/topic/${res.topic_id}`); + unsavedChanges.allowNavigation(); + void navigate(`/topic/${res.topic_id}`); } else { toast.error(res.error_msg || "发布失败"); } diff --git a/apps/web/app/routes/user.$name.tsx b/apps/web/app/routes/user.$name.tsx index a1713a6..ac1323d 100644 --- a/apps/web/app/routes/user.$name.tsx +++ b/apps/web/app/routes/user.$name.tsx @@ -171,7 +171,7 @@ function UserHero({ user, currentUser }: { user: any; currentUser?: any }) { if (res.success) { toast.success(res.message || "操作成功"); setActionTarget(null); - revalidate(); + void revalidate(); } else { toast.error(res.error_msg || "操作失败"); } diff --git a/apps/web/env.d.ts b/apps/web/env.d.ts new file mode 100644 index 0000000..cbe652d --- /dev/null +++ b/apps/web/env.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/apps/web/package.json b/apps/web/package.json index b6571ac..01b0ba0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,6 +8,7 @@ "build": "react-router build", "start": "react-router-serve build/server/index.js", "deploy": "wrangler deploy", + "postinstall": "react-router typegen", "test": "vp test run", "typecheck": "react-router typegen && tsc --noEmit" }, @@ -56,7 +57,7 @@ "shadcn": "4.16.1", "tailwindcss": "^4.0.0", "tw-animate-css": "1.4.0", - "typescript": "^5.7.0", + "typescript": "^7.0.0", "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:", diff --git a/apps/web/tests/UnsavedChanges.test.tsx b/apps/web/tests/UnsavedChanges.test.tsx index f972c73..540c51f 100644 --- a/apps/web/tests/UnsavedChanges.test.tsx +++ b/apps/web/tests/UnsavedChanges.test.tsx @@ -8,7 +8,8 @@ import { useState } from "react"; function Draft() { const [value, setValue] = useState(""); const navigate = useNavigate(); - const { blocker, allowNavigation } = useUnsavedChanges(value !== ""); + const unsavedChanges = useUnsavedChanges(value !== ""); + const { blocker } = unsavedChanges; return ( <> @@ -17,8 +18,8 @@ function Draft() {