From ea8fd838d9585cc19fd56b95620662095f42e813 Mon Sep 17 00:00:00 2001 From: Yeung Lihan Date: Tue, 17 Mar 2026 06:08:21 +0800 Subject: [PATCH] feat(import): normalize missing layout sections for reactive resume json (#2799) * feat(import): normalize missing layout sections for reactive resume json * chore: add comments --- .../import/reactive-resume-json.tsx | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/integrations/import/reactive-resume-json.tsx b/src/integrations/import/reactive-resume-json.tsx index 0e21bfe2d..865bff3e3 100644 --- a/src/integrations/import/reactive-resume-json.tsx +++ b/src/integrations/import/reactive-resume-json.tsx @@ -1,10 +1,69 @@ import { flattenError, ZodError } from "zod"; -import { type ResumeData, resumeDataSchema } from "@/schema/resume/data"; +import { type ResumeData, resumeDataSchema, sectionTypeSchema } from "@/schema/resume/data"; + +const BUILT_IN_LAYOUT_SECTION_IDS = sectionTypeSchema.options.filter((section) => section !== "cover-letter"); + +function normalizeBuiltInSectionsInLayout(data: ResumeData): ResumeData { + const pages = data.metadata.layout.pages; + + // Some exported resumes can arrive without any persisted layout pages. + if (pages.length === 0) { + return { + ...data, + metadata: { + ...data.metadata, + layout: { + ...data.metadata.layout, + pages: [ + { + fullWidth: false, + main: [...BUILT_IN_LAYOUT_SECTION_IDS], + sidebar: [], + }, + ], + }, + }, + }; + } + + const existingSectionIds = new Set(); + + // Preserve the imported layout and only compute which built-in IDs are missing entirely. + for (const page of pages) { + for (const sectionId of page.main) existingSectionIds.add(sectionId); + for (const sectionId of page.sidebar) existingSectionIds.add(sectionId); + } + + const missingSectionIds = BUILT_IN_LAYOUT_SECTION_IDS.filter((sectionId) => !existingSectionIds.has(sectionId)); + + if (missingSectionIds.length === 0) return data; + + const [firstPage, ...restPages] = pages; + + return { + ...data, + metadata: { + ...data.metadata, + layout: { + ...data.metadata.layout, + pages: [ + { + ...firstPage, + // Recover missing built-in sections without reordering the imported layout. + main: [...firstPage.main, ...missingSectionIds], + }, + ...restPages, + ], + }, + }, + }; +} export class ReactiveResumeJSONImporter { parse(json: string): ResumeData { try { - return resumeDataSchema.parse(JSON.parse(json)); + const parsed = resumeDataSchema.parse(JSON.parse(json)); + return resumeDataSchema.parse(normalizeBuiltInSectionsInLayout(parsed)); } catch (error) { if (error instanceof ZodError) { const errors = flattenError(error);