feat: update navigation to support parameter objects and add error-safe persistence to resume store

This commit is contained in:
JOYCEQL
2026-06-15 23:56:28 +08:00
parent 8f8f1fe798
commit f1fc952c67
6 changed files with 60 additions and 14 deletions
@@ -93,7 +93,7 @@ export const ResumeCardItem = ({
onClick={(e) => {
e.stopPropagation();
setActiveResume(id);
router.push(`/app/workbench/${id}`);
router.push({ to: "/app/workbench/$id", params: { id } });
}}
>
<div className="absolute inset-0 pb-6 flex items-center justify-center pointer-events-none transition-transform duration-300 group-hover:scale-[1.02] overflow-hidden" ref={containerRef}>
@@ -139,7 +139,7 @@ export const ResumeCardItem = ({
onClick={(e) => {
e.stopPropagation();
setActiveResume(id);
router.push(`/app/workbench/${id}`);
router.push({ to: "/app/workbench/$id", params: { id } });
}}
className="flex-1 flex items-center justify-center gap-1.5 hover:bg-white dark:hover:bg-gray-800/80 transition-all duration-200 text-gray-700 dark:text-gray-200 hover:text-primary font-medium text-sm group"
>
@@ -100,7 +100,7 @@ export const ResumeWorkbench = () => {
setIsCreateModalOpen(false);
setActiveResume(newId);
router.push(`/app/workbench/${newId}`);
router.push({ to: "/app/workbench/$id", params: { id: newId } });
};
const duplicateResume = async (resume: any) => {
@@ -138,7 +138,7 @@ export const ResumeWorkbench = () => {
setActiveResume(resumeId);
setIsImportDialogOpen(false);
toast.success(t("dashboard.resumes.importSuccess"));
router.push(`/app/workbench/${resumeId}`);
router.push({ to: "/app/workbench/$id", params: { id: resumeId } });
};
const extractImagesFromPdf = async (file: File) => {
@@ -234,7 +234,7 @@ export const ResumeWorkbench = () => {
setActiveResume(resumeId);
setIsImportDialogOpen(false);
toast.success(t("dashboard.resumes.importDialog.pdfSuccess"));
router.push(`/app/workbench/${resumeId}`);
router.push({ to: "/app/workbench/$id", params: { id: resumeId } });
};
const handleJsonFileChange = async (
+1 -1
View File
@@ -264,7 +264,7 @@ const TemplatesPage = () => {
});
}
router.push(`/app/workbench/${resumeId}`);
router.push({ to: "/app/workbench/$id", params: { id: resumeId } });
};
return (
+1 -1
View File
@@ -147,7 +147,7 @@ const PreviewDock = ({
const targetPath = `/app/workbench/${newId}`;
setActiveResume(newId);
toast.success(t("copyResume.success"));
router.push(targetPath);
router.push({ to: "/app/workbench/$id", params: { id: newId } });
requestAnimationFrame(() => {
if (window.location.pathname !== targetPath) {
+13 -3
View File
@@ -1,13 +1,23 @@
import { useLocation, useNavigate } from "@tanstack/react-router";
type NavigateTarget = string;
type NavigateTarget =
| string
| {
to: string;
params?: Record<string, string>;
search?: Record<string, unknown>;
hash?: string;
};
export function useRouter() {
const navigate = useNavigate();
const toNavigateOptions = (target: NavigateTarget) =>
typeof target === "string" ? { to: target } : target;
return {
push: (to: NavigateTarget) => navigate({ to }),
replace: (to: NavigateTarget) => navigate({ to, replace: true }),
push: (target: NavigateTarget) => navigate(toNavigateOptions(target) as any),
replace: (target: NavigateTarget) =>
navigate({ ...toNavigateOptions(target), replace: true } as any),
back: () => window.history.back(),
forward: () => window.history.forward(),
refresh: () => window.location.reload()
+40 -4
View File
@@ -1,5 +1,6 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { createJSONStorage, persist } from "zustand/middleware";
import type { StateStorage } from "zustand/middleware";
import { getFileHandle, verifyPermission } from "@/utils/fileSystem";
import {
BasicInfo,
@@ -75,6 +76,32 @@ interface ResumeStore {
type PersistedResumeStore = Pick<ResumeStore, "resumes" | "activeResumeId">;
const warnedPersistFailures = new Set<string>();
const warnPersistFailure = (name: string, error: unknown) => {
if (warnedPersistFailures.has(name)) {
return;
}
warnedPersistFailures.add(name);
console.warn(
`[resume-store] Failed to persist "${name}" to localStorage. Changes remain available in memory for this session.`,
error
);
};
const createSafeLocalStorage = (): StateStorage => ({
getItem: (name) => localStorage.getItem(name),
setItem: (name, value) => {
try {
localStorage.setItem(name, value);
} catch (error) {
warnPersistFailure(name, error);
}
},
removeItem: (name) => localStorage.removeItem(name),
});
const parseTimestamp = (value?: string): number | null => {
if (!value) {
return null;
@@ -201,7 +228,7 @@ const debouncedSyncToFile = (
};
export const useResumeStore = create(
persist<ResumeStore>(
persist<ResumeStore, [], [], PersistedResumeStore>(
(set, get) => ({
resumes: {},
activeResumeId: null,
@@ -380,8 +407,14 @@ export const useResumeStore = create(
},
setActiveResume: (resumeId) => {
const resume = get().resumes[resumeId];
set({ activeResume: resume ?? null, activeResumeId: resumeId });
const { resumes, activeResume, activeResumeId } = get();
const nextResume = resumes[resumeId] ?? null;
if (activeResumeId === resumeId && activeResume === nextResume) {
return;
}
set({ activeResume: nextResume, activeResumeId: resumeId });
},
updateBasicInfo: (data) => {
@@ -795,6 +828,9 @@ export const useResumeStore = create(
}),
{
name: "resume-storage",
storage: createJSONStorage<PersistedResumeStore>(() =>
createSafeLocalStorage()
),
partialize: (state): PersistedResumeStore => ({
resumes: state.resumes,
activeResumeId: state.activeResumeId,