mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-12 15:52:56 +10:00
Merge branch 'main' into feat/separate-links
This commit is contained in:
@ -12,6 +12,18 @@
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
// react
|
||||
"react/no-unescaped-entities": "off",
|
||||
"react/jsx-sort-props": [
|
||||
"error",
|
||||
{
|
||||
"reservedFirst": true,
|
||||
"callbacksLast": true,
|
||||
"shorthandFirst": true,
|
||||
"noSortAlphabetically": true
|
||||
}
|
||||
],
|
||||
|
||||
// react-hooks
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
const { join } = require("path");
|
||||
const path = require("node:path");
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, "tailwind.config.js"),
|
||||
config: path.join(__dirname, "tailwind.config.js"),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
|
||||
@ -20,7 +20,7 @@ export const Page = ({ mode = "preview", pageNumber, children }: Props) => {
|
||||
return (
|
||||
<div
|
||||
data-page={pageNumber}
|
||||
className={cn("relative bg-white", mode === "builder" && "shadow-2xl")}
|
||||
className={cn("relative bg-background text-foreground", mode === "builder" && "shadow-2xl")}
|
||||
style={{
|
||||
fontFamily,
|
||||
width: `${pageSizeMap[page.format].width * MM_TO_PX}px`,
|
||||
|
||||
@ -4,7 +4,8 @@ import { RouterProvider } from "react-router-dom";
|
||||
|
||||
import { router } from "./router";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const root = ReactDOM.createRoot(document.querySelector("#root")!);
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
|
||||
@ -39,20 +39,20 @@ export const ArtboardPage = () => {
|
||||
`${metadata.typography.lineHeight}`,
|
||||
);
|
||||
|
||||
document.documentElement.style.setProperty("--color-text", `${metadata.theme.text}`);
|
||||
document.documentElement.style.setProperty("--color-primary", `${metadata.theme.primary}`);
|
||||
document.documentElement.style.setProperty(
|
||||
"--color-background",
|
||||
`${metadata.theme.background}`,
|
||||
);
|
||||
document.documentElement.style.setProperty("--color-foreground", metadata.theme.text);
|
||||
document.documentElement.style.setProperty("--color-primary", metadata.theme.primary);
|
||||
document.documentElement.style.setProperty("--color-background", metadata.theme.background);
|
||||
}, [metadata]);
|
||||
|
||||
// Typography Options
|
||||
useEffect(() => {
|
||||
document.querySelectorAll(`[data-page]`).forEach((el) => {
|
||||
// eslint-disable-next-line unicorn/prefer-spread
|
||||
const elements = Array.from(document.querySelectorAll(`[data-page]`));
|
||||
|
||||
for (const el of elements) {
|
||||
el.classList.toggle("hide-icons", metadata.typography.hideIcons);
|
||||
el.classList.toggle("underline-links", metadata.typography.underlineLinks);
|
||||
});
|
||||
}
|
||||
}, [metadata]);
|
||||
|
||||
return <Outlet />;
|
||||
|
||||
@ -38,11 +38,11 @@ export const BuilderLayout = () => {
|
||||
|
||||
return (
|
||||
<TransformWrapper
|
||||
ref={transformRef}
|
||||
centerOnInit
|
||||
maxScale={2}
|
||||
minScale={0.4}
|
||||
initialScale={0.8}
|
||||
ref={transformRef}
|
||||
limitToBounds={false}
|
||||
>
|
||||
<TransformComponent
|
||||
@ -56,8 +56,8 @@ export const BuilderLayout = () => {
|
||||
<AnimatePresence>
|
||||
{layout.map((columns, pageIndex) => (
|
||||
<motion.div
|
||||
layout
|
||||
key={pageIndex}
|
||||
layout
|
||||
initial={{ opacity: 0, x: -200, y: 0 }}
|
||||
animate={{ opacity: 1, x: 0, transition: { delay: pageIndex * 0.3 } }}
|
||||
exit={{ opacity: 0, x: -200 }}
|
||||
|
||||
@ -20,7 +20,10 @@ export const Providers = () => {
|
||||
};
|
||||
|
||||
const resumeData = window.localStorage.getItem("resume");
|
||||
if (resumeData) return setResume(JSON.parse(resumeData));
|
||||
if (resumeData) {
|
||||
setResume(JSON.parse(resumeData));
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
|
||||
@ -34,6 +37,7 @@ export const Providers = () => {
|
||||
// setResume(sampleResume);
|
||||
// }, [setResume]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!resume) return null;
|
||||
|
||||
return <Outlet />;
|
||||
|
||||
@ -8,5 +8,7 @@ export type ArtboardStore = {
|
||||
|
||||
export const useArtboardStore = create<ArtboardStore>()((set) => ({
|
||||
resume: null as unknown as ResumeData,
|
||||
setResume: (resume) => set({ resume }),
|
||||
setResume: (resume) => {
|
||||
set({ resume });
|
||||
},
|
||||
}));
|
||||
|
||||
@ -21,5 +21,5 @@
|
||||
}
|
||||
|
||||
.wysiwyg {
|
||||
@apply prose max-w-none text-current prose-headings:mt-0 prose-headings:mb-2 prose-p:mt-0 prose-p:mb-2 prose-ul:mt-0 prose-ul:mb-2 prose-li:mt-0 prose-li:mb-2 prose-ol:mt-0 prose-ol:mb-2 prose-img:mt-0 prose-img:mb-2 prose-hr:mt-0 prose-hr:mb-2 prose-p:leading-normal prose-li:leading-normal prose-a:break-all;
|
||||
@apply prose max-w-none prose-foreground prose-headings:mt-0 prose-headings:mb-2 prose-p:mt-0 prose-p:mb-2 prose-ul:mt-0 prose-ul:mb-2 prose-li:mt-0 prose-li:mb-2 prose-ol:mt-0 prose-ol:mb-2 prose-img:mt-0 prose-img:mb-2 prose-hr:mt-0 prose-hr:mb-2 prose-p:leading-normal prose-li:leading-normal prose-a:break-all;
|
||||
}
|
||||
|
||||
@ -93,9 +93,9 @@ const Summary = () => {
|
||||
<div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</main>
|
||||
</section>
|
||||
@ -134,7 +134,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && ( icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -189,7 +189,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -227,7 +227,7 @@ const Section = <T,>({
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -272,7 +272,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -480,36 +480,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -85,9 +85,9 @@ const Summary = () => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg col-span-4"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -126,7 +126,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -181,7 +181,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid grid-cols-5 border-t pt-2.5">
|
||||
@ -209,7 +209,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -250,7 +250,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -492,36 +492,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -84,9 +84,9 @@ const Summary = () => {
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -128,7 +128,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -183,7 +183,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -209,7 +209,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -295,7 +295,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -493,36 +493,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -530,7 +544,7 @@ export const Chikorita = ({ columns, isFirstPage = false }: TemplateProps) => {
|
||||
const [main, sidebar] = columns;
|
||||
|
||||
return (
|
||||
<div className="grid h-full grid-cols-3">
|
||||
<div className="grid min-h-[inherit] grid-cols-3">
|
||||
<div className="main p-custom group col-span-2 space-y-4">
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ const Header = () => {
|
||||
<p>{basics.headline}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-text col-span-2 col-start-2 mt-10">
|
||||
<div className="col-span-2 col-start-2 mt-10 text-foreground">
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm">
|
||||
{basics.location && (
|
||||
<>
|
||||
@ -104,9 +104,9 @@ const Summary = () => {
|
||||
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -145,7 +145,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -200,7 +200,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -229,11 +229,11 @@ const Section = <T,>({
|
||||
{url !== undefined && section.separateLinks && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-y-0 -left-px border-l-[4px] border-primary group-[.sidebar]:hidden" />
|
||||
<div className="absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden" />
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -276,7 +276,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -518,36 +518,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -82,9 +82,9 @@ const Summary = () => {
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -123,7 +123,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-background" />)}
|
||||
</div>
|
||||
@ -178,7 +178,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -204,7 +204,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -245,7 +245,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -487,34 +487,47 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -84,9 +84,9 @@ const Summary = () => {
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -131,7 +131,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-primary" />)}
|
||||
</div>
|
||||
@ -186,7 +186,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -214,7 +214,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -300,7 +300,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -497,36 +497,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -15,31 +15,44 @@ import { Rhyhorn } from "./rhyhorn";
|
||||
|
||||
export const getTemplate = (template: Template) => {
|
||||
switch (template) {
|
||||
case "azurill":
|
||||
case "azurill": {
|
||||
return Azurill;
|
||||
case "bronzor":
|
||||
}
|
||||
case "bronzor": {
|
||||
return Bronzor;
|
||||
case "chikorita":
|
||||
}
|
||||
case "chikorita": {
|
||||
return Chikorita;
|
||||
case "ditto":
|
||||
}
|
||||
case "ditto": {
|
||||
return Ditto;
|
||||
case "gengar":
|
||||
}
|
||||
case "gengar": {
|
||||
return Gengar;
|
||||
case "glalie":
|
||||
}
|
||||
case "glalie": {
|
||||
return Glalie;
|
||||
case "kakuna":
|
||||
}
|
||||
case "kakuna": {
|
||||
return Kakuna;
|
||||
case "leafish":
|
||||
}
|
||||
case "leafish": {
|
||||
return Leafish;
|
||||
case "nosepass":
|
||||
}
|
||||
case "nosepass": {
|
||||
return Nosepass;
|
||||
case "onyx":
|
||||
}
|
||||
case "onyx": {
|
||||
return Onyx;
|
||||
case "pikachu":
|
||||
}
|
||||
case "pikachu": {
|
||||
return Pikachu;
|
||||
case "rhyhorn":
|
||||
}
|
||||
case "rhyhorn": {
|
||||
return Rhyhorn;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
return Onyx;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -110,9 +110,9 @@ const Summary = () => {
|
||||
</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -151,7 +151,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -206,7 +206,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -231,7 +231,7 @@ const Section = <T,>({
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -448,34 +448,47 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "summary":
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -44,9 +44,9 @@ const Header = () => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -148,7 +148,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && ( icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -203,7 +203,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -228,7 +228,7 @@ const Section = <T,>({
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -445,32 +445,44 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "experience":
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -98,9 +98,9 @@ const Summary = () => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@ -127,7 +127,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -181,7 +181,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className={cn("grid", dateKey !== undefined && "gap-y-4")}>
|
||||
@ -218,7 +218,7 @@ const Section = <T,>({
|
||||
{url !== undefined && section.separateLinks && <Link url={url} />}
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
@ -253,7 +253,7 @@ const Section = <T,>({
|
||||
{url !== undefined && section.separateLinks && <Link url={url} />}
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
@ -294,7 +294,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -495,36 +495,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -536,7 +550,7 @@ export const Nosepass = ({ columns, isFirstPage = false }: TemplateProps) => {
|
||||
return (
|
||||
<div className="p-custom space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<img alt="Europass Logo" className="h-[42px]" src="https://i.imgur.com/eRK005p.png" />
|
||||
<img alt="Europass Logo" className="h-[42px]" src="/assets/europass.png" />
|
||||
|
||||
<p className="font-medium text-primary">Curriculum Vitae</p>
|
||||
|
||||
|
||||
@ -113,9 +113,9 @@ const Summary = () => {
|
||||
<h4 className="font-bold text-primary">{section.name}</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -154,7 +154,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && ( icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -209,7 +209,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -235,7 +235,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -486,34 +486,47 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "summary":
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -105,9 +105,9 @@ const Summary = () => {
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -154,7 +154,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && ( icon ?? <i className="ph ph-bold ph-link text-primary group-[.summary]:text-background" />)}
|
||||
</div>
|
||||
@ -209,7 +209,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -235,7 +235,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -276,7 +276,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -518,36 +518,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -85,9 +85,9 @@ const Summary = () => {
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -126,7 +126,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
{label ?? (url.label || url.href)}
|
||||
</a>
|
||||
{iconOnRight && ( icon ?? <i className="ph ph-bold ph-link text-primary" />)}
|
||||
</div>
|
||||
@ -181,7 +181,7 @@ const Section = <T,>({
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
if (!section.visible || section.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
@ -207,7 +207,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
||||
)}
|
||||
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
@ -248,7 +248,7 @@ const Profiles = () => {
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
{!item.icon && <p className="text-sm">{item.network}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -490,36 +490,50 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
case "profiles": {
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
}
|
||||
case "summary": {
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
}
|
||||
case "experience": {
|
||||
return <Experience />;
|
||||
case "education":
|
||||
}
|
||||
case "education": {
|
||||
return <Education />;
|
||||
case "awards":
|
||||
}
|
||||
case "awards": {
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
}
|
||||
case "certifications": {
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
}
|
||||
case "skills": {
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
}
|
||||
case "interests": {
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
}
|
||||
case "publications": {
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
}
|
||||
case "volunteer": {
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
}
|
||||
case "languages": {
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
}
|
||||
case "projects": {
|
||||
return <Projects />;
|
||||
case "references":
|
||||
}
|
||||
case "references": {
|
||||
return <References />;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -4,8 +4,3 @@ export type TemplateProps = {
|
||||
columns: SectionKey[][];
|
||||
isFirstPage?: boolean;
|
||||
};
|
||||
|
||||
export type BaseProps = {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
text: "var(--color-text)",
|
||||
foreground: "var(--color-foreground)",
|
||||
primary: "var(--color-primary)",
|
||||
background: "var(--color-background)",
|
||||
},
|
||||
@ -23,6 +23,28 @@ module.exports = {
|
||||
loose: "calc(var(--line-height) + 0.5)",
|
||||
},
|
||||
spacing: { custom: "var(--margin)" },
|
||||
typography: () => ({
|
||||
foreground: {
|
||||
css: {
|
||||
"--tw-prose-body": "var(--color-foreground)",
|
||||
"--tw-prose-headings": "var(--color-foreground)",
|
||||
"--tw-prose-lead": "var(--color-foreground)",
|
||||
"--tw-prose-links": "var(--color-foreground)",
|
||||
"--tw-prose-bold": "var(--color-foreground)",
|
||||
"--tw-prose-counters": "var(--color-foreground)",
|
||||
"--tw-prose-bullets": "var(--color-foreground)",
|
||||
"--tw-prose-hr": "var(--color-foreground)",
|
||||
"--tw-prose-quotes": "var(--color-foreground)",
|
||||
"--tw-prose-quote-borders": "var(--color-foreground)",
|
||||
"--tw-prose-captions": "var(--color-foreground)",
|
||||
"--tw-prose-code": "var(--color-foreground)",
|
||||
"--tw-prose-pre-code": "var(--color-foreground)",
|
||||
"--tw-prose-pre-bg": "var(--color-background)",
|
||||
"--tw-prose-th-borders": "var(--color-foreground)",
|
||||
"--tw-prose-td-borders": "var(--color-foreground)",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { defineConfig, searchForWorkspaceRoot, splitVendorChunkPlugin } from "vite";
|
||||
import { defineConfig, searchForWorkspaceRoot } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
base: "/artboard/",
|
||||
@ -11,15 +11,16 @@ export default defineConfig({
|
||||
|
||||
build: {
|
||||
sourcemap: true,
|
||||
emptyOutDir: true,
|
||||
},
|
||||
|
||||
server: {
|
||||
host: true,
|
||||
port: +(process.env.__DEV__ARTBOARD_PORT ?? 6173),
|
||||
port: 6173,
|
||||
fs: { allow: [searchForWorkspaceRoot(process.cwd())] },
|
||||
},
|
||||
|
||||
plugins: [react(), nxViteTsPaths(), splitVendorChunkPlugin()],
|
||||
plugins: [react(), nxViteTsPaths()],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@ -11,11 +11,24 @@
|
||||
"settings": {
|
||||
"tailwindcss": {
|
||||
"callees": ["cn", "clsx", "cva"],
|
||||
"config": "tailwind.config.js"
|
||||
"config": "tailwind.config.js",
|
||||
"whitelist": ["ph", "ph-"]
|
||||
}
|
||||
},
|
||||
"plugins": ["lingui"],
|
||||
"rules": {
|
||||
// react
|
||||
"react/no-unescaped-entities": "off",
|
||||
"react/jsx-sort-props": [
|
||||
"error",
|
||||
{
|
||||
"reservedFirst": true,
|
||||
"callbacksLast": true,
|
||||
"shorthandFirst": true,
|
||||
"noSortAlphabetically": true
|
||||
}
|
||||
],
|
||||
|
||||
// react-hooks
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
|
||||
|
||||
@ -35,10 +35,13 @@
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="/src/styles/main.css" />
|
||||
</head>
|
||||
<body class="bg-background text-foreground text-sm antialiased print:bg-white print:m-0">
|
||||
<body class="text-sm antialiased bg-background text-foreground print:bg-white print:m-0">
|
||||
<div id="root"></div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<!-- Phosphor Icons -->
|
||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
const { join } = require("path");
|
||||
const path = require("node:path");
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-import": {},
|
||||
"tailwindcss/nesting": {},
|
||||
tailwindcss: { config: join(__dirname, "tailwind.config.js") },
|
||||
tailwindcss: { config: path.join(__dirname, "tailwind.config.js") },
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
BIN
apps/client/public/assets/europass.png
Normal file
BIN
apps/client/public/assets/europass.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@ -54,7 +54,7 @@ export const AiActions = ({ value, onChange, className }: Props) => {
|
||||
toast({
|
||||
variant: "error",
|
||||
title: t`Oops, the server returned an error.`,
|
||||
description: (error as Error)?.message,
|
||||
description: (error as Error).message,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@ -27,10 +27,7 @@ export const Copyright = ({ className }: Props) => (
|
||||
<span>{t`By the community, for the community.`}</span>
|
||||
<span>
|
||||
<Trans>
|
||||
A passion project by{" "}
|
||||
<a target="_blank" rel="noopener noreferrer nofollow" href="https://www.amruthpillai.com/">
|
||||
Amruth Pillai
|
||||
</a>
|
||||
A passion project by <a href="https://www.amruthpillai.com/">Amruth Pillai</a>
|
||||
</Trans>
|
||||
</span>
|
||||
|
||||
|
||||
@ -12,12 +12,14 @@ export const Icon = ({ size = 32, className }: Props) => {
|
||||
let src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
|
||||
switch (isDarkMode) {
|
||||
case false:
|
||||
case false: {
|
||||
src = "/icon/dark.svg";
|
||||
break;
|
||||
case true:
|
||||
}
|
||||
case true: {
|
||||
src = "/icon/light.svg";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -38,8 +38,8 @@ export const LocaleCombobox = ({ value, onValueChange }: Props) => {
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
placeholder={t`Search for a language`}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{t`No results found`}</CommandEmpty>
|
||||
@ -48,10 +48,10 @@ export const LocaleCombobox = ({ value, onValueChange }: Props) => {
|
||||
<div className="max-h-60">
|
||||
{options.map(({ original }) => (
|
||||
<CommandItem
|
||||
disabled={false}
|
||||
key={original.locale}
|
||||
disabled={false}
|
||||
value={original.locale.trim()}
|
||||
onSelect={async (selectedValue) => {
|
||||
onSelect={(selectedValue) => {
|
||||
const result = options.find(
|
||||
({ original }) => original.locale.trim() === selectedValue,
|
||||
);
|
||||
|
||||
@ -12,12 +12,14 @@ export const Logo = ({ size = 32, className }: Props) => {
|
||||
let src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
|
||||
switch (isDarkMode) {
|
||||
case false:
|
||||
case false: {
|
||||
src = "/logo/light.svg";
|
||||
break;
|
||||
case true:
|
||||
}
|
||||
case true: {
|
||||
src = "/logo/dark.svg";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -12,9 +12,18 @@ export const UserAvatar = ({ size = 36, className }: Props) => {
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
let picture: React.ReactNode = null;
|
||||
let picture: React.ReactNode;
|
||||
|
||||
if (!user.picture) {
|
||||
if (user.picture) {
|
||||
picture = (
|
||||
<img
|
||||
alt={user.name}
|
||||
src={user.picture}
|
||||
className="rounded-full"
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const initials = getInitials(user.name);
|
||||
|
||||
picture = (
|
||||
@ -25,15 +34,6 @@ export const UserAvatar = ({ size = 36, className }: Props) => {
|
||||
{initials}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
picture = (
|
||||
<img
|
||||
alt={user.name}
|
||||
src={user.picture}
|
||||
className="rounded-full"
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={className}>{picture}</div>;
|
||||
|
||||
@ -24,7 +24,11 @@ export const UserOptions = ({ children }: Props) => {
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent side="top" align="start" className="w-48">
|
||||
<DropdownMenuItem onClick={() => navigate("/dashboard/settings")}>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
navigate("/dashboard/settings");
|
||||
}}
|
||||
>
|
||||
{t`Settings`}
|
||||
{/* eslint-disable-next-line lingui/no-unlocalized-strings */}
|
||||
<KeyboardShortcut>⇧S</KeyboardShortcut>
|
||||
|
||||
@ -40,9 +40,9 @@ type Action =
|
||||
toastId?: ToasterToast["id"];
|
||||
};
|
||||
|
||||
interface State {
|
||||
type State = {
|
||||
toasts: ToasterToast[];
|
||||
}
|
||||
};
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
@ -64,17 +64,19 @@ const addToRemoveQueue = (toastId: string) => {
|
||||
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "ADD_TOAST":
|
||||
case "ADD_TOAST": {
|
||||
return {
|
||||
...state,
|
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||
};
|
||||
}
|
||||
|
||||
case "UPDATE_TOAST":
|
||||
case "UPDATE_TOAST": {
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
||||
};
|
||||
}
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action;
|
||||
@ -82,9 +84,9 @@ export const reducer = (state: State, action: Action): State => {
|
||||
if (toastId) {
|
||||
addToRemoveQueue(toastId);
|
||||
} else {
|
||||
state.toasts.forEach((toast) => {
|
||||
for (const toast of state.toasts) {
|
||||
addToRemoveQueue(toast.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@ -99,7 +101,7 @@ export const reducer = (state: State, action: Action): State => {
|
||||
),
|
||||
};
|
||||
}
|
||||
case "REMOVE_TOAST":
|
||||
case "REMOVE_TOAST": {
|
||||
if (action.toastId === undefined) {
|
||||
return {
|
||||
...state,
|
||||
@ -110,18 +112,19 @@ export const reducer = (state: State, action: Action): State => {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const listeners: Array<(state: State) => void> = [];
|
||||
const listeners: ((state: State) => void)[] = [];
|
||||
|
||||
let memoryState: State = { toasts: [] };
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action);
|
||||
listeners.forEach((listener) => {
|
||||
for (const listener of listeners) {
|
||||
listener(memoryState);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">;
|
||||
@ -129,12 +132,15 @@ type Toast = Omit<ToasterToast, "id">;
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = createId();
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
const update = (props: ToasterToast) => {
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
});
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||
};
|
||||
const dismiss = () => {
|
||||
dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
@ -170,7 +176,9 @@ function useToast() {
|
||||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
dismiss: (toastId?: string) => {
|
||||
dispatch({ type: "DISMISS_TOAST", toastId });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,18 +4,13 @@ import _axios from "axios";
|
||||
import createAuthRefreshInterceptor from "axios-auth-refresh";
|
||||
import { redirect } from "react-router-dom";
|
||||
|
||||
import { refreshToken } from "@/client/services/auth";
|
||||
|
||||
import { USER_KEY } from "../constants/query-keys";
|
||||
import { toast } from "../hooks/use-toast";
|
||||
import { refresh } from "../services/auth/refresh";
|
||||
import { translateError } from "../services/errors/translate-error";
|
||||
import { queryClient } from "./query-client";
|
||||
|
||||
export type ServerError = {
|
||||
statusCode: number;
|
||||
message: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export const axios = _axios.create({ baseURL: "/api", withCredentials: true });
|
||||
|
||||
// Intercept responses to transform ISO dates to JS date objects
|
||||
@ -36,7 +31,7 @@ axios.interceptors.response.use(
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
return Promise.reject(new Error(message));
|
||||
},
|
||||
);
|
||||
|
||||
@ -45,26 +40,12 @@ axios.interceptors.response.use(
|
||||
const axiosForRefresh = _axios.create({ baseURL: "/api", withCredentials: true });
|
||||
|
||||
// Interceptor to handle expired access token errors
|
||||
const handleAuthError = async () => {
|
||||
try {
|
||||
await refresh(axiosForRefresh);
|
||||
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
const handleAuthError = () => refreshToken(axiosForRefresh);
|
||||
|
||||
// Interceptor to handle expired refresh token errors
|
||||
const handleRefreshError = async () => {
|
||||
try {
|
||||
queryClient.invalidateQueries({ queryKey: USER_KEY });
|
||||
redirect("/auth/login");
|
||||
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
await queryClient.invalidateQueries({ queryKey: USER_KEY });
|
||||
redirect("/auth/login");
|
||||
};
|
||||
|
||||
// Intercept responses to check for 401 and 403 errors, refresh token and retry the request
|
||||
|
||||
@ -13,6 +13,7 @@ export async function dynamicActivate(locale: string) {
|
||||
i18n.loadAndActivate({ locale, messages });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (dayjsLocales[locale]) {
|
||||
dayjs.locale(await dayjsLocales[locale]());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1703
apps/client/src/locales/ms-MY/messages.po
Normal file
1703
apps/client/src/locales/ms-MY/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1703
apps/client/src/locales/sq-AL/messages.po
Normal file
1703
apps/client/src/locales/sq-AL/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,37 @@
|
||||
import { StrictMode } from "react";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { StrictMode, useEffect } from "react";
|
||||
import * as ReactDOM from "react-dom/client";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import {
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
RouterProvider,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
} from "react-router-dom";
|
||||
|
||||
import { router } from "./router";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
if (import.meta.env.VITE_CLIENT_SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_CLIENT_SENTRY_DSN,
|
||||
integrations: [
|
||||
Sentry.reactRouterV6BrowserTracingIntegration({
|
||||
useEffect,
|
||||
matchRoutes,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
}),
|
||||
Sentry.replayIntegration(),
|
||||
],
|
||||
tracesSampleRate: 1,
|
||||
replaysOnErrorSampleRate: 1,
|
||||
replaysSessionSampleRate: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const root = ReactDOM.createRoot(document.querySelector("#root")!);
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
|
||||
@ -40,7 +40,7 @@ export const BackupOtpPage = () => {
|
||||
await backupOtp(data);
|
||||
|
||||
navigate("/dashboard");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
form.reset();
|
||||
}
|
||||
};
|
||||
@ -87,7 +87,13 @@ export const BackupOtpPage = () => {
|
||||
/>
|
||||
|
||||
<div className="mt-4 flex items-center gap-x-2">
|
||||
<Button variant="link" className="px-5" onClick={() => navigate(-1)}>
|
||||
<Button
|
||||
variant="link"
|
||||
className="px-5"
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
>
|
||||
<ArrowLeft size={14} className="mr-2" />
|
||||
<span>{t`Back`}</span>
|
||||
</Button>
|
||||
|
||||
@ -89,7 +89,13 @@ export const ForgotPasswordPage = () => {
|
||||
/>
|
||||
|
||||
<div className="mt-4 flex items-center gap-x-2">
|
||||
<Button variant="link" className="px-5" onClick={() => navigate(-1)}>
|
||||
<Button
|
||||
variant="link"
|
||||
className="px-5"
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
>
|
||||
<ArrowLeft size={14} className="mr-2" />
|
||||
<span>{t`Back`}</span>
|
||||
</Button>
|
||||
|
||||
@ -30,7 +30,7 @@ export const LoginPage = () => {
|
||||
const { login, loading } = useLogin();
|
||||
|
||||
const { providers } = useAuthProviders();
|
||||
const emailAuthDisabled = !providers || !providers.includes("email");
|
||||
const emailAuthDisabled = !providers?.includes("email");
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
usePasswordToggle(formRef);
|
||||
@ -43,7 +43,7 @@ export const LoginPage = () => {
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
try {
|
||||
await login(data);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
form.reset();
|
||||
}
|
||||
};
|
||||
|
||||
@ -34,7 +34,7 @@ export const RegisterPage = () => {
|
||||
const disableSignups = import.meta.env.VITE_DISABLE_SIGNUPS === "true";
|
||||
|
||||
const { providers } = useAuthProviders();
|
||||
const emailAuthDisabled = !providers || !providers.includes("email");
|
||||
const emailAuthDisabled = !providers?.includes("email");
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
usePasswordToggle(formRef);
|
||||
@ -55,7 +55,7 @@ export const RegisterPage = () => {
|
||||
await register(data);
|
||||
|
||||
navigate("/auth/verify-email");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
form.reset();
|
||||
}
|
||||
};
|
||||
|
||||
@ -26,7 +26,7 @@ type FormValues = z.infer<typeof resetPasswordSchema>;
|
||||
export const ResetPasswordPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get("token") || "";
|
||||
const token = searchParams.get("token") ?? "";
|
||||
|
||||
const { resetPassword, loading } = useResetPassword();
|
||||
|
||||
@ -43,7 +43,7 @@ export const ResetPasswordPage = () => {
|
||||
await resetPassword(data);
|
||||
|
||||
navigate("/auth/login");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
form.reset();
|
||||
}
|
||||
};
|
||||
|
||||
@ -33,7 +33,7 @@ export const VerifyEmailPage = () => {
|
||||
|
||||
if (!token) return;
|
||||
|
||||
handleVerifyEmail(token);
|
||||
void handleVerifyEmail(token);
|
||||
}, [token, navigate, verifyEmail]);
|
||||
|
||||
return (
|
||||
|
||||
@ -40,7 +40,7 @@ export const VerifyOtpPage = () => {
|
||||
await verifyOtp(data);
|
||||
|
||||
navigate("/dashboard");
|
||||
} catch (error) {
|
||||
} catch {
|
||||
form.reset();
|
||||
}
|
||||
};
|
||||
|
||||
@ -18,7 +18,9 @@ export const BuilderHeader = () => {
|
||||
const leftPanelSize = useBuilderStore((state) => state.panel.left.size);
|
||||
const rightPanelSize = useBuilderStore((state) => state.panel.right.size);
|
||||
|
||||
const onToggle = (side: "left" | "right") => toggle(side);
|
||||
const onToggle = (side: "left" | "right") => {
|
||||
toggle(side);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -33,7 +35,9 @@ export const BuilderHeader = () => {
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="flex lg:hidden"
|
||||
onClick={() => onToggle("left")}
|
||||
onClick={() => {
|
||||
onToggle("left");
|
||||
}}
|
||||
>
|
||||
<SidebarSimple />
|
||||
</Button>
|
||||
@ -60,7 +64,9 @@ export const BuilderHeader = () => {
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="flex lg:hidden"
|
||||
onClick={() => onToggle("right")}
|
||||
onClick={() => {
|
||||
onToggle("right");
|
||||
}}
|
||||
>
|
||||
<SidebarSimple className="-scale-x-100" />
|
||||
</Button>
|
||||
|
||||
@ -20,6 +20,11 @@ import { usePrintResume } from "@/client/services/resume";
|
||||
import { useBuilderStore } from "@/client/stores/builder";
|
||||
import { useResumeStore, useTemporalResumeStore } from "@/client/stores/resume";
|
||||
|
||||
const openInNewTab = (url: string) => {
|
||||
const win = window.open(url, "_blank");
|
||||
if (win) win.focus();
|
||||
};
|
||||
|
||||
export const BuilderToolbar = () => {
|
||||
const { toast } = useToast();
|
||||
const setValue = useResumeStore((state) => state.setValue);
|
||||
@ -36,11 +41,6 @@ export const BuilderToolbar = () => {
|
||||
const onPrint = async () => {
|
||||
const { url } = await printResume({ id });
|
||||
|
||||
const openInNewTab = (url: string) => {
|
||||
const win = window.open(url, "_blank");
|
||||
if (win) win.focus();
|
||||
};
|
||||
|
||||
openInNewTab(url);
|
||||
};
|
||||
|
||||
@ -64,13 +64,27 @@ export const BuilderToolbar = () => {
|
||||
<motion.div className="fixed inset-x-0 bottom-0 mx-auto hidden py-6 text-center md:block">
|
||||
<div className="inline-flex items-center justify-center rounded-full bg-background px-4 shadow-xl">
|
||||
<Tooltip content={t`Undo`}>
|
||||
<Button size="icon" variant="ghost" className="rounded-none" onClick={() => undo()}>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
onClick={() => {
|
||||
undo();
|
||||
}}
|
||||
>
|
||||
<ArrowCounterClockwise />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={t`Redo`}>
|
||||
<Button size="icon" variant="ghost" className="rounded-none" onClick={() => redo()}>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
onClick={() => {
|
||||
redo();
|
||||
}}
|
||||
>
|
||||
<ArrowClockwise />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@ -134,8 +148,8 @@ export const BuilderToolbar = () => {
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="rounded-none"
|
||||
onClick={onCopy}
|
||||
disabled={!isPublic}
|
||||
onClick={onCopy}
|
||||
>
|
||||
<LinkSimple />
|
||||
</Button>
|
||||
@ -145,9 +159,9 @@ export const BuilderToolbar = () => {
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={onPrint}
|
||||
disabled={loading}
|
||||
className="rounded-none"
|
||||
onClick={onPrint}
|
||||
>
|
||||
{loading ? <CircleNotch className="animate-spin" /> : <FilePdf />}
|
||||
</Button>
|
||||
|
||||
@ -10,6 +10,10 @@ import { BuilderToolbar } from "./_components/toolbar";
|
||||
import { LeftSidebar } from "./sidebars/left";
|
||||
import { RightSidebar } from "./sidebars/right";
|
||||
|
||||
const onOpenAutoFocus = (event: Event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const OutletSlot = () => (
|
||||
<>
|
||||
<BuilderHeader />
|
||||
@ -33,8 +37,6 @@ export const BuilderLayout = () => {
|
||||
const leftHandle = useBuilderStore((state) => state.panel.left.handle);
|
||||
const rightHandle = useBuilderStore((state) => state.panel.right.handle);
|
||||
|
||||
const onOpenAutoFocus = (event: Event) => event.preventDefault();
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<div className="relative size-full overflow-hidden">
|
||||
@ -43,8 +45,8 @@ export const BuilderLayout = () => {
|
||||
minSize={25}
|
||||
maxSize={45}
|
||||
defaultSize={30}
|
||||
onResize={leftSetSize}
|
||||
className={cn("z-10 bg-background", !leftHandle.isDragging && "transition-[flex]")}
|
||||
onResize={leftSetSize}
|
||||
>
|
||||
<LeftSidebar />
|
||||
</Panel>
|
||||
@ -63,8 +65,8 @@ export const BuilderLayout = () => {
|
||||
minSize={25}
|
||||
maxSize={45}
|
||||
defaultSize={30}
|
||||
onResize={rightSetSize}
|
||||
className={cn("z-10 bg-background", !rightHandle.isDragging && "transition-[flex]")}
|
||||
onResize={rightSetSize}
|
||||
>
|
||||
<RightSidebar />
|
||||
</Panel>
|
||||
@ -79,8 +81,8 @@ export const BuilderLayout = () => {
|
||||
<SheetContent
|
||||
side="left"
|
||||
showClose={false}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
className="top-16 p-0 sm:max-w-xl"
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<LeftSidebar />
|
||||
</SheetContent>
|
||||
@ -92,8 +94,8 @@ export const BuilderLayout = () => {
|
||||
<SheetContent
|
||||
side="right"
|
||||
showClose={false}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
className="top-16 p-0 sm:max-w-xl"
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<RightSidebar />
|
||||
</SheetContent>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user