diff --git a/src/components/resume/shared/items/awards-item.tsx b/src/components/resume/shared/items/awards-item.tsx
index ff7005fe1..a7e26f960 100644
--- a/src/components/resume/shared/items/awards-item.tsx
+++ b/src/components/resume/shared/items/awards-item.tsx
@@ -15,10 +15,10 @@ export function AwardsItem({ className, ...item }: AwardsItemProps) {
diff --git a/src/components/resume/shared/items/education-item.tsx b/src/components/resume/shared/items/education-item.tsx
index c7af6591c..ec390c4e7 100644
--- a/src/components/resume/shared/items/education-item.tsx
+++ b/src/components/resume/shared/items/education-item.tsx
@@ -15,13 +15,13 @@ export function EducationItem({ className, ...item }: EducationItemProps) {
{item.school}
-
+
{[item.degree, item.grade].filter(Boolean).join(" • ")}
diff --git a/src/components/resume/shared/items/interests-item.tsx b/src/components/resume/shared/items/interests-item.tsx
index 1ea09f7b3..b85fcf16c 100644
--- a/src/components/resume/shared/items/interests-item.tsx
+++ b/src/components/resume/shared/items/interests-item.tsx
@@ -11,7 +11,7 @@ export function InterestsItem({ className, ...item }: InterestsItemProps) {
diff --git a/src/components/resume/shared/items/languages-item.tsx b/src/components/resume/shared/items/languages-item.tsx
index 893c1f111..1e29fdb4b 100644
--- a/src/components/resume/shared/items/languages-item.tsx
+++ b/src/components/resume/shared/items/languages-item.tsx
@@ -10,10 +10,10 @@ export function LanguagesItem({ className, ...item }: LanguagesItemProps) {
return (
-
+
{item.language}
-
{item.fluency}
+
{item.fluency}
diff --git a/src/components/resume/shared/items/profiles-item.tsx b/src/components/resume/shared/items/profiles-item.tsx
index 91bd3bc45..23d4e55fc 100644
--- a/src/components/resume/shared/items/profiles-item.tsx
+++ b/src/components/resume/shared/items/profiles-item.tsx
@@ -12,7 +12,7 @@ export function ProfilesItem({ className, ...item }: ProfilesItemProps) {
diff --git a/src/components/resume/shared/items/projects-item.tsx b/src/components/resume/shared/items/projects-item.tsx
index e62963585..05586106c 100644
--- a/src/components/resume/shared/items/projects-item.tsx
+++ b/src/components/resume/shared/items/projects-item.tsx
@@ -15,7 +15,7 @@ export function ProjectsItem({ className, ...item }: ProjectsItemProps) {
{item.name}
-
{item.period}
+
{item.period}
diff --git a/src/components/resume/shared/items/publications-item.tsx b/src/components/resume/shared/items/publications-item.tsx
index a238fca34..3c35dc4f9 100644
--- a/src/components/resume/shared/items/publications-item.tsx
+++ b/src/components/resume/shared/items/publications-item.tsx
@@ -15,10 +15,10 @@ export function PublicationsItem({ className, ...item }: PublicationsItemProps)
{item.title}
-
{item.date}
+
{item.date}
-
{item.publisher}
+
{item.publisher}
diff --git a/src/components/resume/shared/items/references-item.tsx b/src/components/resume/shared/items/references-item.tsx
index fef55eebe..f85cd4e41 100644
--- a/src/components/resume/shared/items/references-item.tsx
+++ b/src/components/resume/shared/items/references-item.tsx
@@ -12,17 +12,17 @@ export function ReferencesItem({ className, ...item }: ReferencesItemProps) {
-
{item.position}
+
{item.position}
-
{item.phone}
+
{item.phone}
diff --git a/src/components/resume/shared/items/skills-item.tsx b/src/components/resume/shared/items/skills-item.tsx
index 2e4ece104..78cbed476 100644
--- a/src/components/resume/shared/items/skills-item.tsx
+++ b/src/components/resume/shared/items/skills-item.tsx
@@ -12,13 +12,13 @@ export function SkillsItem({ className, ...item }: SkillsItemProps) {
-
{item.proficiency}
+
{item.proficiency}
{item.keywords.join(", ")}
diff --git a/src/components/resume/shared/items/volunteer-item.tsx b/src/components/resume/shared/items/volunteer-item.tsx
index ff3740a48..4cc1e9187 100644
--- a/src/components/resume/shared/items/volunteer-item.tsx
+++ b/src/components/resume/shared/items/volunteer-item.tsx
@@ -15,10 +15,10 @@ export function VolunteerItem({ className, ...item }: VolunteerItemProps) {
{item.organization}
-
{item.period}
+
{item.period}
-
{item.location}
+
{item.location}
diff --git a/src/integrations/import/reactive-resume-v4-json.tsx b/src/integrations/import/reactive-resume-v4-json.tsx
index db37cfadf..9a278d186 100644
--- a/src/integrations/import/reactive-resume-v4-json.tsx
+++ b/src/integrations/import/reactive-resume-v4-json.tsx
@@ -236,8 +236,8 @@ type V4ResumeData = {
items: Array<{
id: string;
visible: boolean;
- language?: string;
- fluency?: string;
+ name?: string;
+ description?: string;
level?: number;
}>;
};
@@ -391,6 +391,20 @@ type V4ResumeData = {
};
};
+// Transform layout section ID from V4 format to new format
+// V4 uses "custom.{id}" for custom sections, new format just uses "{id}"
+const transformLayoutSectionId = (id: string): string => {
+ if (id.startsWith("custom.")) return id.slice(7);
+ return id;
+};
+
+// Transform layout column by stripping "custom." prefix from section IDs
+const transformLayoutColumn = (column: string[]): string[] => {
+ return column
+ .filter((id) => id !== "summary") // Summary is handled separately
+ .map(transformLayoutSectionId);
+};
+
export class ReactiveResumeV4JSONImporter {
parse(json: string): ResumeData {
try {
@@ -532,12 +546,12 @@ export class ReactiveResumeV4JSONImporter {
columns: v4Data.sections.languages?.columns ?? 1,
hidden: !(v4Data.sections.languages?.visible ?? true),
items: (v4Data.sections.languages?.items ?? [])
- .filter((item) => item.language && item.language.length > 0)
+ .filter((item) => item.name && item.name.length > 0)
.map((item) => ({
id: item.id ?? generateId(),
hidden: !(item.visible ?? true),
- language: item.language!,
- fluency: item.fluency ?? "",
+ language: item.name!,
+ fluency: item.description ?? "",
level: clampLevel(item.level ?? 0),
})),
},
@@ -680,12 +694,12 @@ export class ReactiveResumeV4JSONImporter {
layout: {
sidebarWidth: clampSidebarWidth(35),
pages: (v4Data.metadata.layout ?? []).map((page) => {
- const main = page[0] ?? [];
- const sidebar = page[1] ?? [];
+ const main = transformLayoutColumn(page[0] ?? []);
+ const sidebar = transformLayoutColumn(page[1] ?? []);
return {
fullWidth: sidebar.length === 0,
- main: main.filter((id) => id !== "summary"),
- sidebar: sidebar,
+ main,
+ sidebar,
};
}),
},
diff --git a/src/integrations/orpc/services/resume.ts b/src/integrations/orpc/services/resume.ts
index c09dbd576..3da5e5bbd 100644
--- a/src/integrations/orpc/services/resume.ts
+++ b/src/integrations/orpc/services/resume.ts
@@ -147,13 +147,19 @@ export const resumeService = {
if (!resume) throw new ORPCError("NOT_FOUND");
- // Convert picture URL to base64 data, so there's no fetching required on the client.
- const url = resume.data.picture.url.replace(env.APP_URL, "http://localhost:3000");
- const base64 = await fetch(url)
- .then((res) => res.arrayBuffer())
- .then((buffer) => Buffer.from(buffer).toString("base64"));
+ try {
+ if (!resume.data.picture.url) throw new Error("Picture is not available");
- resume.data.picture.url = `data:image/jpeg;base64,${base64}`;
+ // Convert picture URL to base64 data, so there's no fetching required on the client.
+ const url = resume.data.picture.url.replace(env.APP_URL, "http://localhost:3000");
+ const base64 = await fetch(url)
+ .then((res) => res.arrayBuffer())
+ .then((buffer) => Buffer.from(buffer).toString("base64"));
+
+ resume.data.picture.url = `data:image/jpeg;base64,${base64}`;
+ } catch {
+ // Ignore errors, as the picture is not always available
+ }
return resume;
},