mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-26 09:54:43 +10:00
- Use browserless over gotenberg
- Implement functionality to move items between sections or pages - Enhance custom sections to have a `type` property - Update the v4 importer to account for custom sections - Update healthcheck to be a simple curl command - Update dependencies to latest and a lot more changes
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { match } from "ts-pattern";
|
||||
import type { SectionType } from "@/schema/resume/data";
|
||||
import type { CustomSectionItem, SectionType } from "@/schema/resume/data";
|
||||
import { cn } from "@/utils/style";
|
||||
import { useResumeStore } from "../store/resume";
|
||||
import { AwardsItem } from "./items/awards-item";
|
||||
import { CertificationsItem } from "./items/certifications-item";
|
||||
import { EducationItem } from "./items/education-item";
|
||||
@@ -12,7 +14,6 @@ import { PublicationsItem } from "./items/publications-item";
|
||||
import { ReferencesItem } from "./items/references-item";
|
||||
import { SkillsItem } from "./items/skills-item";
|
||||
import { VolunteerItem } from "./items/volunteer-item";
|
||||
import { PageCustomSection } from "./page-custom";
|
||||
import { PageSection } from "./page-section";
|
||||
import { PageSummary } from "./page-summary";
|
||||
|
||||
@@ -21,6 +22,44 @@ type SectionComponentProps = {
|
||||
itemClassName?: string;
|
||||
};
|
||||
|
||||
// Helper to render item component based on type
|
||||
function renderItemByType(type: SectionType, item: CustomSectionItem, itemClassName?: string) {
|
||||
return match(type)
|
||||
.with("profiles", () => (
|
||||
<ProfilesItem {...(item as Parameters<typeof ProfilesItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("experience", () => (
|
||||
<ExperienceItem {...(item as Parameters<typeof ExperienceItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("education", () => (
|
||||
<EducationItem {...(item as Parameters<typeof EducationItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("projects", () => (
|
||||
<ProjectsItem {...(item as Parameters<typeof ProjectsItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("skills", () => <SkillsItem {...(item as Parameters<typeof SkillsItem>[0])} className={itemClassName} />)
|
||||
.with("languages", () => (
|
||||
<LanguagesItem {...(item as Parameters<typeof LanguagesItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("interests", () => (
|
||||
<InterestsItem {...(item as Parameters<typeof InterestsItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("awards", () => <AwardsItem {...(item as Parameters<typeof AwardsItem>[0])} className={itemClassName} />)
|
||||
.with("certifications", () => (
|
||||
<CertificationsItem {...(item as Parameters<typeof CertificationsItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("publications", () => (
|
||||
<PublicationsItem {...(item as Parameters<typeof PublicationsItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("volunteer", () => (
|
||||
<VolunteerItem {...(item as Parameters<typeof VolunteerItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.with("references", () => (
|
||||
<ReferencesItem {...(item as Parameters<typeof ReferencesItem>[0])} className={itemClassName} />
|
||||
))
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
export function getSectionComponent(
|
||||
section: "summary" | SectionType | (string & {}),
|
||||
{ sectionClassName, itemClassName }: SectionComponentProps = {},
|
||||
@@ -127,9 +166,39 @@ export function getSectionComponent(
|
||||
return ReferencesSection;
|
||||
})
|
||||
.otherwise(() => {
|
||||
const CustomSection = ({ id }: { id: string }) => (
|
||||
<PageCustomSection sectionId={id} className={sectionClassName} />
|
||||
);
|
||||
return CustomSection;
|
||||
// Custom section - render based on its type
|
||||
const CustomSectionComponent = ({ id }: { id: string }) => {
|
||||
const customSection = useResumeStore((state) => state.resume.data.customSections.find((s) => s.id === id));
|
||||
|
||||
if (!customSection) return null;
|
||||
if (customSection.hidden) return null;
|
||||
if (customSection.items.length === 0) return null;
|
||||
|
||||
const visibleItems = customSection.items.filter((item) => !item.hidden);
|
||||
if (visibleItems.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(`page-section page-section-custom page-section-${id} wrap-break-word`, sectionClassName)}
|
||||
>
|
||||
<h6 className="mb-1 text-(--page-primary-color)">{customSection.title}</h6>
|
||||
|
||||
<div
|
||||
className="section-content grid gap-x-(--page-gap-x) gap-y-(--page-gap-y)"
|
||||
style={{ gridTemplateColumns: `repeat(${customSection.columns}, 1fr)` }}
|
||||
>
|
||||
{visibleItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(`section-item section-item-${customSection.type} wrap-break-word *:space-y-1`)}
|
||||
>
|
||||
{renderItemByType(customSection.type, item, itemClassName)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
return CustomSectionComponent;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { TiptapContent } from "@/components/input/rich-input";
|
||||
import { cn } from "@/utils/style";
|
||||
import { useResumeStore } from "../store/resume";
|
||||
|
||||
type PageCustomSectionProps = {
|
||||
sectionId: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function PageCustomSection({ sectionId, className }: PageCustomSectionProps) {
|
||||
const section = useResumeStore((state) =>
|
||||
state.resume.data.customSections.find((section) => section.id === sectionId),
|
||||
);
|
||||
|
||||
// biome-ignore lint/complexity/noUselessFragments: render empty fragment, instead of null
|
||||
if (!section) return <></>;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
`page-section page-section-custom page-section-${sectionId} wrap-break-word`,
|
||||
section.hidden && "hidden",
|
||||
section.content === "" && "hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<h6 className="mb-2 text-(--page-primary-color)">{section.title}</h6>
|
||||
|
||||
<div className="section-content">
|
||||
<TiptapContent style={{ columnCount: section.columns }} content={section.content} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export function AzurillTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
const { main, sidebar, fullWidth } = pageLayout;
|
||||
|
||||
return (
|
||||
<div className="template-azurill page-content space-y-(--page-gap-y) px-(--page-margin-x) py-(--page-margin-y)">
|
||||
<div className="template-azurill page-content space-y-(--page-gap-y) px-(--page-margin-x) py-(--page-margin-y) print:p-0">
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<div className="flex gap-x-(--page-gap-x)">
|
||||
|
||||
@@ -23,7 +23,7 @@ export function BronzorTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
const { main, sidebar, fullWidth } = pageLayout;
|
||||
|
||||
return (
|
||||
<div className="template-bronzor page-content space-y-(--page-gap-y) px-(--page-margin-x) py-(--page-margin-y)">
|
||||
<div className="template-bronzor page-content space-y-(--page-gap-y) px-(--page-margin-x) py-(--page-margin-y) print:p-0">
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<div className="space-y-(--page-gap-y)">
|
||||
|
||||
@@ -20,7 +20,7 @@ export function KakunaTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
const { main, sidebar, fullWidth } = pageLayout;
|
||||
|
||||
return (
|
||||
<div className="template-kakuna page-content space-y-4 px-(--page-margin-x) py-(--page-margin-y)">
|
||||
<div className="template-kakuna page-content space-y-4 px-(--page-margin-x) py-(--page-margin-y) print:p-0">
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<main data-layout="main" className="group page-main space-y-4">
|
||||
|
||||
@@ -17,7 +17,7 @@ export function OnyxTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
const { main, sidebar, fullWidth } = pageLayout;
|
||||
|
||||
return (
|
||||
<div className="template-onyx page-content space-y-(--page-gap-y) px-(--page-margin-x) py-(--page-margin-y)">
|
||||
<div className="template-onyx page-content space-y-(--page-gap-y) px-(--page-margin-x) py-(--page-margin-y) print:p-0">
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<main data-layout="main" className="group page-main space-y-(--page-gap-y)">
|
||||
|
||||
@@ -24,7 +24,7 @@ export function PikachuTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
const { main, sidebar, fullWidth } = pageLayout;
|
||||
|
||||
return (
|
||||
<div className="template-pikachu page-content px-(--page-margin-x) py-(--page-margin-y)">
|
||||
<div className="template-pikachu page-content px-(--page-margin-x) py-(--page-margin-y) print:p-0">
|
||||
<div className="flex gap-x-(--page-margin-x)">
|
||||
<aside
|
||||
data-layout="sidebar"
|
||||
|
||||
@@ -20,7 +20,7 @@ export function RhyhornTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
const { main, sidebar, fullWidth } = pageLayout;
|
||||
|
||||
return (
|
||||
<div className="template-rhyhorn page-content space-y-4 px-(--page-margin-x) py-(--page-margin-y)">
|
||||
<div className="template-rhyhorn page-content space-y-4 px-(--page-margin-x) py-(--page-margin-y) print:p-0">
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<main data-layout="main" className="group page-main space-y-4">
|
||||
|
||||
Reference in New Issue
Block a user