mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-27 02:14:50 +10:00
normalize section items with consistent css classes
This commit is contained in:
@@ -15,10 +15,10 @@ export function AwardsItem({ className, ...item }: AwardsItemProps) {
|
|||||||
<p className="section-item-title awards-item-title">
|
<p className="section-item-title awards-item-title">
|
||||||
<strong>{item.title}</strong>
|
<strong>{item.title}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata text-right">{item.date}</p>
|
<p className="section-item-metadata awards-item-date text-right">{item.date}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="section-item-awarder awards-item-awarder">{item.awarder}</p>
|
<p className="section-item-metadata awards-item-awarder">{item.awarder}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description awards-item-description">
|
<div className="section-item-description awards-item-description">
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export function CertificationsItem({ className, ...item }: CertificationsItemPro
|
|||||||
<p className="section-item-title certifications-item-title">
|
<p className="section-item-title certifications-item-title">
|
||||||
<strong>{item.title}</strong>
|
<strong>{item.title}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata text-right">{item.date}</p>
|
<p className="section-item-metadata certifications-item-date text-right">{item.date}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="section-item-issuer certifications-item-issuer">{item.issuer}</p>
|
<p className="section-item-metadata certifications-item-issuer">{item.issuer}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description certifications-item-description">
|
<div className="section-item-description certifications-item-description">
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ export function EducationItem({ className, ...item }: EducationItemProps) {
|
|||||||
<p className="section-item-title education-item-title">
|
<p className="section-item-title education-item-title">
|
||||||
<strong>{item.school}</strong>
|
<strong>{item.school}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata education-item-metadata text-right">
|
<p className="section-item-metadata education-item-degree-grade text-right">
|
||||||
{[item.degree, item.grade].filter(Boolean).join(" • ")}
|
{[item.degree, item.grade].filter(Boolean).join(" • ")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p>{item.area}</p>
|
<p className="section-item-metadata education-item-area">{item.area}</p>
|
||||||
<p className="section-item-metadata education-item-metadata text-right">
|
<p className="section-item-metadata education-item-location-period text-right">
|
||||||
{[item.location, item.period].filter(Boolean).join(" • ")}
|
{[item.location, item.period].filter(Boolean).join(" • ")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ export function ExperienceItem({ className, ...item }: ExperienceItemProps) {
|
|||||||
<p className="section-item-title experience-item-title">
|
<p className="section-item-title experience-item-title">
|
||||||
<strong>{item.company}</strong>
|
<strong>{item.company}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata experience-item-metadata text-right">{item.location}</p>
|
<p className="section-item-metadata experience-item-location text-right">{item.location}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p>{item.position}</p>
|
<p className="section-item-metadata experience-item-position">{item.position}</p>
|
||||||
<p className="section-item-metadata experience-item-metadata text-right">{item.period}</p>
|
<p className="section-item-metadata experience-item-period text-right">{item.period}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description experience-item-description">
|
<div className="section-item-description experience-item-description">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function InterestsItem({ className, ...item }: InterestsItemProps) {
|
|||||||
<div className={cn("interests-item", className)}>
|
<div className={cn("interests-item", className)}>
|
||||||
<div className="section-item-header flex items-center gap-x-1.5">
|
<div className="section-item-header flex items-center gap-x-1.5">
|
||||||
<PageIcon icon={item.icon} className="section-item-icon interests-item-icon shrink-0" />
|
<PageIcon icon={item.icon} className="section-item-icon interests-item-icon shrink-0" />
|
||||||
<p className="section-item-name interests-item-name">
|
<p className="section-item-title interests-item-name">
|
||||||
<strong>{item.name}</strong>
|
<strong>{item.name}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export function LanguagesItem({ className, ...item }: LanguagesItemProps) {
|
|||||||
return (
|
return (
|
||||||
<div className={cn("languages-item", className)}>
|
<div className={cn("languages-item", className)}>
|
||||||
<div className="section-item-header">
|
<div className="section-item-header">
|
||||||
<p className="section-item-name languages-item-name">
|
<p className="section-item-title languages-item-name">
|
||||||
<strong>{item.language}</strong>
|
<strong>{item.language}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-fluency languages-item-fluency opacity-80">{item.fluency}</p>
|
<p className="section-item-metadata languages-item-fluency opacity-80">{item.fluency}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PageLevel level={item.level} className="section-item-level languages-item-level" />
|
<PageLevel level={item.level} className="section-item-level languages-item-level" />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function ProfilesItem({ className, ...item }: ProfilesItemProps) {
|
|||||||
<div className={cn("profiles-item", className)}>
|
<div className={cn("profiles-item", className)}>
|
||||||
<div className="section-item-header flex items-center gap-x-1.5">
|
<div className="section-item-header flex items-center gap-x-1.5">
|
||||||
<PageIcon icon={item.icon} className="section-item-icon profiles-item-icon shrink-0" />
|
<PageIcon icon={item.icon} className="section-item-icon profiles-item-icon shrink-0" />
|
||||||
<p className="section-item-network profiles-item-network">
|
<p className="section-item-title profiles-item-network">
|
||||||
<strong>{item.network}</strong>
|
<strong>{item.network}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function ProjectsItem({ className, ...item }: ProjectsItemProps) {
|
|||||||
<p className="section-item-title projects-item-title">
|
<p className="section-item-title projects-item-title">
|
||||||
<strong>{item.name}</strong>
|
<strong>{item.name}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata text-right">{item.period}</p>
|
<p className="section-item-metadata projects-item-period text-right">{item.period}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description projects-item-description">
|
<div className="section-item-description projects-item-description">
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export function PublicationsItem({ className, ...item }: PublicationsItemProps)
|
|||||||
<p className="section-item-title publications-item-title">
|
<p className="section-item-title publications-item-title">
|
||||||
<strong>{item.title}</strong>
|
<strong>{item.title}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata text-right">{item.date}</p>
|
<p className="section-item-metadata publications-item-date text-right">{item.date}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="section-item-publisher publications-item-publisher">{item.publisher}</p>
|
<p className="section-item-metadata publications-item-publisher">{item.publisher}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description publications-item-description">
|
<div className="section-item-description publications-item-description">
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ export function ReferencesItem({ className, ...item }: ReferencesItemProps) {
|
|||||||
<div className={cn("references-item", className)}>
|
<div className={cn("references-item", className)}>
|
||||||
<div className="section-item-header references-item-header">
|
<div className="section-item-header references-item-header">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="section-item-name references-item-name">
|
<p className="section-item-title references-item-name">
|
||||||
<strong>{item.name}</strong>
|
<strong>{item.name}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="section-item-subtitle references-item-position">{item.position}</p>
|
<p className="section-item-metadata references-item-position">{item.position}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description references-item-description">
|
<div className="section-item-description references-item-description">
|
||||||
<TiptapContent content={item.description} />
|
<TiptapContent content={item.description} />
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-footer references-item-footer flex flex-col">
|
<div className="section-item-footer references-item-footer flex flex-col">
|
||||||
<div className="section-item-metadata references-item-phone">{item.phone}</div>
|
<p className="section-item-metadata references-item-phone">{item.phone}</p>
|
||||||
<PageLink {...item.website} label={item.website.label} className="section-item-link references-item-link" />
|
<PageLink {...item.website} label={item.website.label} className="section-item-link references-item-link" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export function SkillsItem({ className, ...item }: SkillsItemProps) {
|
|||||||
<div className={cn("skills-item", className)}>
|
<div className={cn("skills-item", className)}>
|
||||||
<div className="section-item-header flex items-center gap-x-1.5">
|
<div className="section-item-header flex items-center gap-x-1.5">
|
||||||
<PageIcon icon={item.icon} className="section-item-icon skills-item-icon shrink-0" />
|
<PageIcon icon={item.icon} className="section-item-icon skills-item-icon shrink-0" />
|
||||||
<p className="section-item-name skills-item-name">
|
<p className="section-item-title skills-item-name">
|
||||||
<strong>{item.name}</strong>
|
<strong>{item.name}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="section-item-proficiency skills-item-proficiency opacity-80">{item.proficiency}</p>
|
<p className="section-item-metadata skills-item-proficiency opacity-80">{item.proficiency}</p>
|
||||||
<small className="section-item-keywords skills-item-keywords">{item.keywords.join(", ")}</small>
|
<small className="section-item-keywords skills-item-keywords">{item.keywords.join(", ")}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export function VolunteerItem({ className, ...item }: VolunteerItemProps) {
|
|||||||
<p className="section-item-title volunteer-item-title">
|
<p className="section-item-title volunteer-item-title">
|
||||||
<strong>{item.organization}</strong>
|
<strong>{item.organization}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="section-item-metadata text-right">{item.period}</p>
|
<p className="section-item-metadata volunteer-item-period text-right">{item.period}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="section-item-location volunteer-item-location">{item.location}</p>
|
<p className="section-item-metadata volunteer-item-location">{item.location}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="section-item-description volunteer-item-description">
|
<div className="section-item-description volunteer-item-description">
|
||||||
|
|||||||
@@ -236,8 +236,8 @@ type V4ResumeData = {
|
|||||||
items: Array<{
|
items: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
language?: string;
|
name?: string;
|
||||||
fluency?: string;
|
description?: string;
|
||||||
level?: number;
|
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 {
|
export class ReactiveResumeV4JSONImporter {
|
||||||
parse(json: string): ResumeData {
|
parse(json: string): ResumeData {
|
||||||
try {
|
try {
|
||||||
@@ -532,12 +546,12 @@ export class ReactiveResumeV4JSONImporter {
|
|||||||
columns: v4Data.sections.languages?.columns ?? 1,
|
columns: v4Data.sections.languages?.columns ?? 1,
|
||||||
hidden: !(v4Data.sections.languages?.visible ?? true),
|
hidden: !(v4Data.sections.languages?.visible ?? true),
|
||||||
items: (v4Data.sections.languages?.items ?? [])
|
items: (v4Data.sections.languages?.items ?? [])
|
||||||
.filter((item) => item.language && item.language.length > 0)
|
.filter((item) => item.name && item.name.length > 0)
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
id: item.id ?? generateId(),
|
id: item.id ?? generateId(),
|
||||||
hidden: !(item.visible ?? true),
|
hidden: !(item.visible ?? true),
|
||||||
language: item.language!,
|
language: item.name!,
|
||||||
fluency: item.fluency ?? "",
|
fluency: item.description ?? "",
|
||||||
level: clampLevel(item.level ?? 0),
|
level: clampLevel(item.level ?? 0),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -680,12 +694,12 @@ export class ReactiveResumeV4JSONImporter {
|
|||||||
layout: {
|
layout: {
|
||||||
sidebarWidth: clampSidebarWidth(35),
|
sidebarWidth: clampSidebarWidth(35),
|
||||||
pages: (v4Data.metadata.layout ?? []).map((page) => {
|
pages: (v4Data.metadata.layout ?? []).map((page) => {
|
||||||
const main = page[0] ?? [];
|
const main = transformLayoutColumn(page[0] ?? []);
|
||||||
const sidebar = page[1] ?? [];
|
const sidebar = transformLayoutColumn(page[1] ?? []);
|
||||||
return {
|
return {
|
||||||
fullWidth: sidebar.length === 0,
|
fullWidth: sidebar.length === 0,
|
||||||
main: main.filter((id) => id !== "summary"),
|
main,
|
||||||
sidebar: sidebar,
|
sidebar,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -147,13 +147,19 @@ export const resumeService = {
|
|||||||
|
|
||||||
if (!resume) throw new ORPCError("NOT_FOUND");
|
if (!resume) throw new ORPCError("NOT_FOUND");
|
||||||
|
|
||||||
// Convert picture URL to base64 data, so there's no fetching required on the client.
|
try {
|
||||||
const url = resume.data.picture.url.replace(env.APP_URL, "http://localhost:3000");
|
if (!resume.data.picture.url) throw new Error("Picture is not available");
|
||||||
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}`;
|
// 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;
|
return resume;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user