Merge branch 'main' into feat/separate-links

This commit is contained in:
Amruth Pillai
2024-05-20 17:01:16 +02:00
318 changed files with 36303 additions and 27864 deletions

View File

@ -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",

View File

@ -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: {},
},

View File

@ -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`,

View File

@ -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>

View File

@ -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 />;

View File

@ -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 }}

View File

@ -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 />;

View File

@ -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 });
},
}));

View File

@ -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;
}

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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 />}

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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>

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -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;
}
}
};

View File

@ -4,8 +4,3 @@ export type TemplateProps = {
columns: SectionKey[][];
isFirstPage?: boolean;
};
export type BaseProps = {
children?: React.ReactNode;
className?: string;
};

View File

@ -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")],

View File

@ -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: {

View File

@ -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",

View File

@ -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>

View File

@ -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: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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);

View File

@ -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>

View File

@ -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 (

View File

@ -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,
);

View File

@ -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 (

View File

@ -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>;

View File

@ -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>

View File

@ -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 });
},
};
}

View File

@ -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

View File

@ -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

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -33,7 +33,7 @@ export const VerifyEmailPage = () => {
if (!token) return;
handleVerifyEmail(token);
void handleVerifyEmail(token);
}, [token, navigate, verifyEmail]);
return (

View File

@ -40,7 +40,7 @@ export const VerifyOtpPage = () => {
await verifyOtp(data);
navigate("/dashboard");
} catch (error) {
} catch {
form.reset();
}
};

View File

@ -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>

View File

@ -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>

View File

@ -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