design nosepass template, add tests, add template previews
@ -55,7 +55,7 @@ STORAGE_SECRET_KEY=minioadmin
|
||||
REDIS_URL=redis://default:password@localhost:6379
|
||||
|
||||
# Sentry (for error reporting, Optional)
|
||||
# SENTRY_DSN=
|
||||
# VITE_SENTRY_DSN=
|
||||
|
||||
# Crowdin (Optional)
|
||||
CROWDIN_PROJECT_ID=
|
||||
|
||||
1
.gitignore
vendored
@ -39,6 +39,7 @@ Thumbs.db
|
||||
# Generated Files
|
||||
.nx
|
||||
.swc
|
||||
fly.toml
|
||||
stats.html
|
||||
libs/prisma
|
||||
|
||||
|
||||
4
.vscode/settings.json
vendored
@ -10,7 +10,5 @@
|
||||
"tools/compose/*"
|
||||
]
|
||||
},
|
||||
"i18n-ally.localesPaths": [
|
||||
"apps/client/src/locales"
|
||||
]
|
||||
"i18n-ally.localesPaths": ["apps/client/src/locales"]
|
||||
}
|
||||
|
||||
24
Dockerfile
@ -1,36 +1,38 @@
|
||||
# --- Base Image ---
|
||||
FROM node:bullseye-slim AS base
|
||||
WORKDIR /app
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
ARG NX_CLOUD_ACCESS_TOKEN
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# --- Build Image ---
|
||||
FROM base AS build
|
||||
|
||||
ENV NX_CLOUD_ACCESS_TOKEN=$NX_CLOUD_ACCESS_TOKEN
|
||||
|
||||
COPY .npmrc package.json package-lock.json ./
|
||||
RUN npm ci && npm cache clean --force
|
||||
COPY .npmrc package.json pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
RUN pnpm build
|
||||
|
||||
# --- Release Image ---
|
||||
FROM base AS release
|
||||
|
||||
RUN apt update && apt install -y dumb-init --no-install-recommends
|
||||
|
||||
COPY --chown=node:node --from=build /app/.npmrc /app/package.json /app/package-lock.json ./
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
COPY --chown=node:node --from=build /app/.npmrc /app/package.json /app/pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
# Copy Build Output
|
||||
COPY --chown=node:node --from=build /app/dist ./dist
|
||||
# Copy Prisma Generated Client
|
||||
COPY --chown=node:node --from=build /app/node_modules/.prisma/client ./node_modules/.prisma/client
|
||||
# Copy Prisma Schema & Migrations
|
||||
COPY --chown=node:node --from=build /app/tools/prisma ./tools/prisma
|
||||
RUN pnpm prisma:generate
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "dumb-init", "npm", "run", "start" ]
|
||||
CMD [ "dumb-init", "pnpm", "start" ]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||

|
||||
[](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume)
|
||||
|
||||
@ -8,6 +8,7 @@ type PictureProps = {
|
||||
|
||||
export const Picture = ({ className }: PictureProps) => {
|
||||
const picture = useArtboardStore((state) => state.resume.basics.picture);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
if (!isUrl(picture.url) || picture.effects.hidden) return null;
|
||||
|
||||
@ -15,11 +16,17 @@ export const Picture = ({ className }: PictureProps) => {
|
||||
<img
|
||||
src={picture.url}
|
||||
alt="Profile"
|
||||
className={cn("relative z-20 object-cover", className)}
|
||||
className={cn(
|
||||
"relative z-20 object-cover",
|
||||
picture.effects.border && "border-primary",
|
||||
picture.effects.grayscale && "grayscale",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
maxWidth: `${picture.size}px`,
|
||||
aspectRatio: `${picture.aspectRatio}`,
|
||||
borderRadius: `${picture.borderRadius}px`,
|
||||
borderWidth: `${picture.effects.border ? fontSize / 3 : 0}px`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { SectionKey } from "@reactive-resume/schema";
|
||||
import { pageSizeMap, TemplateKey } from "@reactive-resume/utils";
|
||||
import { pageSizeMap, Template } from "@reactive-resume/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
||||
@ -12,7 +12,7 @@ export const BuilderLayout = () => {
|
||||
const transformRef = useRef<ReactZoomPanPinchRef>(null);
|
||||
const format = useArtboardStore((state) => state.resume.metadata.page.format);
|
||||
const layout = useArtboardStore((state) => state.resume.metadata.layout);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as TemplateKey);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
|
||||
|
||||
const Template = useMemo(() => getTemplate(template), [template]);
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { SectionKey } from "@reactive-resume/schema";
|
||||
import { TemplateKey } from "@reactive-resume/utils";
|
||||
import { Template } from "@reactive-resume/utils";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Page } from "../components/page";
|
||||
@ -8,7 +8,7 @@ import { getTemplate } from "../templates";
|
||||
|
||||
export const PreviewLayout = () => {
|
||||
const layout = useArtboardStore((state) => state.resume.metadata.layout);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as TemplateKey);
|
||||
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
|
||||
|
||||
const Template = useMemo(() => getTemplate(template), [template]);
|
||||
|
||||
|
||||
@ -19,5 +19,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;
|
||||
@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;
|
||||
}
|
||||
|
||||
@ -79,11 +79,11 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<div className="mb-2 hidden font-bold uppercase text-primary group-[.main]:block">
|
||||
<div className="mb-2 hidden font-bold text-primary group-[.main]:block">
|
||||
<h4>{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="mb-2 hidden items-center gap-x-2 text-center font-bold uppercase text-primary group-[.sidebar]:flex">
|
||||
<div className="mb-2 hidden items-center gap-x-2 text-center font-bold text-primary group-[.sidebar]:flex">
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
<h4>{section.name}</h4>
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
@ -162,18 +162,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<div className="mb-2 hidden font-bold uppercase text-primary group-[.main]:block">
|
||||
<div className="mb-2 hidden font-bold text-primary group-[.main]:block">
|
||||
<h4>{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mb-2 hidden items-center gap-x-2 text-center font-bold uppercase text-primary group-[.sidebar]:flex">
|
||||
<div className="mx-auto mb-2 hidden items-center gap-x-2 text-center font-bold text-primary group-[.sidebar]:flex">
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
<h4>{section.name}</h4>
|
||||
<div className="h-1.5 w-1.5 rounded-full border border-primary" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="grid gap-3 group-[.sidebar]:mx-auto group-[.sidebar]:text-center"
|
||||
className="grid gap-x-6 gap-y-3 group-[.sidebar]:mx-auto group-[.sidebar]:text-center"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -193,19 +193,19 @@ const Section = <T,>({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="leading-snug">{children?.(item as T)}</div>
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
|
||||
<div className="absolute left-[-4.5px] top-px hidden h-[8px] w-[8px] rounded-full bg-primary group-[.main]:block" />
|
||||
</div>
|
||||
@ -223,7 +223,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -258,7 +258,7 @@ const Experience = () => {
|
||||
<div className="font-bold">{item.company}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -276,7 +276,7 @@ const Education = () => {
|
||||
<div>{item.area}</div>
|
||||
<div>{item.score}</div>
|
||||
<div>{item.studyType}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -292,7 +292,7 @@ const Awards = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.title}</div>
|
||||
<div>{item.awarder}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -308,7 +308,7 @@ const Certifications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.issuer}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -349,7 +349,7 @@ const Publications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.publisher}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -366,7 +366,7 @@ const Volunteer = () => {
|
||||
<div className="font-bold">{item.organization}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -377,11 +377,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -399,7 +399,7 @@ const Projects = () => {
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -438,7 +438,7 @@ const Custom = ({ id }: { id: string }) => {
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -158,7 +158,7 @@ const Section = <T,>({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="col-span-4 grid gap-3"
|
||||
className="col-span-4 grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -171,18 +171,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -200,7 +200,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -384,11 +384,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Experience,
|
||||
Interest,
|
||||
Language,
|
||||
Profile,
|
||||
Project,
|
||||
Publication,
|
||||
Reference,
|
||||
@ -26,8 +27,6 @@ import { TemplateProps } from "../types/template";
|
||||
|
||||
const Header = () => {
|
||||
const basics = useArtboardStore((state) => state.resume.basics);
|
||||
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3">
|
||||
@ -71,31 +70,6 @@ const Header = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{profiles.visible && profiles.items.length > 0 && (
|
||||
<div className="flex items-center gap-x-3 gap-y-0.5">
|
||||
{profiles.items
|
||||
.filter((item) => item.visible)
|
||||
.map((item) => (
|
||||
<div key={item.id} className="flex items-center gap-x-2">
|
||||
<Link
|
||||
url={item.url}
|
||||
label={item.username}
|
||||
className="text-sm"
|
||||
icon={
|
||||
<img
|
||||
className="ph"
|
||||
width={fontSize}
|
||||
height={fontSize}
|
||||
alt={item.network}
|
||||
src={`https://cdn.simpleicons.org/${item.icon}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -109,7 +83,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -184,10 +158,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -200,18 +174,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -267,6 +241,38 @@ const Education = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Profiles = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.profiles);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
label={item.username}
|
||||
icon={
|
||||
<img
|
||||
className="ph"
|
||||
width={fontSize}
|
||||
height={fontSize}
|
||||
alt={item.network}
|
||||
src={`https://cdn.simpleicons.org/${item.icon}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Awards = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.awards);
|
||||
|
||||
@ -381,11 +387,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -457,6 +463,8 @@ const Custom = ({ id }: { id: string }) => {
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
|
||||
@ -101,7 +101,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -173,10 +173,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -193,21 +193,21 @@ const Section = <T,>({
|
||||
className={cn("relative space-y-2 pl-4 group-[.sidebar]:pl-0", className)}
|
||||
>
|
||||
<div className="relative -ml-4 group-[.sidebar]:ml-0">
|
||||
<div className="pl-4 leading-snug group-[.sidebar]:pl-0">
|
||||
<div className="pl-4 group-[.sidebar]:pl-0">
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-y-0 -left-px border-l-[4px] border-primary group-[.sidebar]:hidden" />
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
|
||||
@ -227,7 +227,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -411,11 +411,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { TemplateKey } from "@reactive-resume/utils";
|
||||
import { Template } from "@reactive-resume/utils";
|
||||
|
||||
import { Azurill } from "./azurill";
|
||||
import { Bronzor } from "./bronzor";
|
||||
import { Chikorita } from "./chikorita";
|
||||
import { Ditto } from "./ditto";
|
||||
import { Kakuna } from "./kakuna";
|
||||
import { Nosepass } from "./nosepass";
|
||||
import { Onyx } from "./onyx";
|
||||
import { Pikachu } from "./pikachu";
|
||||
import { Rhyhorn } from "./rhyhorn";
|
||||
|
||||
export const getTemplate = (template: TemplateKey) => {
|
||||
export const getTemplate = (template: Template) => {
|
||||
switch (template) {
|
||||
case "onyx":
|
||||
return Onyx;
|
||||
@ -27,6 +28,8 @@ export const getTemplate = (template: TemplateKey) => {
|
||||
return Bronzor;
|
||||
case "pikachu":
|
||||
return Pikachu;
|
||||
case "nosepass":
|
||||
return Nosepass;
|
||||
default:
|
||||
return Onyx;
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold uppercase text-primary">
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold text-primary">
|
||||
{section.name}
|
||||
</h4>
|
||||
|
||||
@ -179,12 +179,12 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold uppercase text-primary">
|
||||
<h4 className="mb-2 border-b border-primary text-center font-bold text-primary">
|
||||
{section.name}
|
||||
</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -197,19 +197,19 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">{children?.(item as T)}</div>
|
||||
<div>{children?.(item as T)}</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@ -228,7 +228,7 @@ const Experience = () => {
|
||||
<div className="font-bold">{item.company}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -246,7 +246,7 @@ const Education = () => {
|
||||
<div>{item.area}</div>
|
||||
<div>{item.score}</div>
|
||||
<div>{item.studyType}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -262,7 +262,7 @@ const Awards = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.title}</div>
|
||||
<div>{item.awarder}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -278,7 +278,7 @@ const Certifications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.issuer}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -319,7 +319,7 @@ const Publications = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.publisher}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -336,7 +336,7 @@ const Volunteer = () => {
|
||||
<div className="font-bold">{item.organization}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -347,11 +347,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
@ -368,7 +368,7 @@ const Projects = () => {
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -407,7 +407,7 @@ const Custom = ({ id }: { id: string }) => {
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
<div>{item.location}</div>
|
||||
<div className="font-bold italic">{item.date}</div>
|
||||
<div className="font-bold">{item.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
529
apps/artboard/src/templates/nosepass.tsx
Normal file
@ -0,0 +1,529 @@
|
||||
import {
|
||||
Award,
|
||||
Certification,
|
||||
CustomSection,
|
||||
CustomSectionGroup,
|
||||
Education,
|
||||
Experience,
|
||||
Interest,
|
||||
Language,
|
||||
Profile,
|
||||
Project,
|
||||
Publication,
|
||||
Reference,
|
||||
SectionKey,
|
||||
SectionWithItem,
|
||||
Skill,
|
||||
URL,
|
||||
Volunteer,
|
||||
} from "@reactive-resume/schema";
|
||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
||||
import get from "lodash.get";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { Picture } from "../components/picture";
|
||||
import { useArtboardStore } from "../store/artboard";
|
||||
import { TemplateProps } from "../types/template";
|
||||
|
||||
const Header = () => {
|
||||
const basics = useArtboardStore((state) => state.resume.basics);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-x-6">
|
||||
<div className="mt-1 space-y-2 text-right">
|
||||
<p className="font-medium text-primary">Personal Information</p>
|
||||
<Picture className="ml-auto" />
|
||||
</div>
|
||||
|
||||
<div className="col-span-3 space-y-2">
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{basics.name}</div>
|
||||
<div className="text-base">{basics.headline}</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 text-sm">
|
||||
{basics.location && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<i className="ph ph-bold ph-map-pin text-primary" />
|
||||
<div>{basics.location}</div>
|
||||
</div>
|
||||
)}
|
||||
{basics.phone && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<i className="ph ph-bold ph-phone text-primary" />
|
||||
<a href={`tel:${basics.phone}`} target="_blank" rel="noreferrer">
|
||||
{basics.phone}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{basics.email && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<i className="ph ph-bold ph-at text-primary" />
|
||||
<a href={`mailto:${basics.email}`} target="_blank" rel="noreferrer">
|
||||
{basics.email}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
<Link url={basics.url} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-x-3 text-sm">
|
||||
{basics.customFields.map((item) => (
|
||||
<div key={item.id} className="flex items-center gap-x-1.5">
|
||||
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
|
||||
<span className="text-primary">{item.name}</span>
|
||||
<span>{item.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Summary = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.summary);
|
||||
|
||||
if (!section.visible || isEmptyString(section.content)) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid grid-cols-4 gap-x-6">
|
||||
<div className="text-right">
|
||||
<h4 className="font-medium text-primary">{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<div className="relative">
|
||||
<hr className="mt-3 border-primary pb-3" />
|
||||
<div className="absolute bottom-3 right-0 h-3 w-3 bg-primary" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
style={{ columns: section.columns }}
|
||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
type LinkProps = {
|
||||
url: URL;
|
||||
icon?: React.ReactNode;
|
||||
label?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Link = ({ url, icon, label, className }: LinkProps) => {
|
||||
if (!isUrl(url.href)) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
{icon ?? <i className="ph ph-bold ph-link text-primary" />}
|
||||
<a
|
||||
href={url.href}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
className={cn("inline-block", className)}
|
||||
>
|
||||
{label || url.label || url.href}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type SectionProps<T> = {
|
||||
section: SectionWithItem<T> | CustomSectionGroup;
|
||||
children?: (item: T) => React.ReactNode;
|
||||
urlKey?: keyof T;
|
||||
dateKey?: keyof T;
|
||||
levelKey?: keyof T;
|
||||
summaryKey?: keyof T;
|
||||
keywordsKey?: keyof T;
|
||||
};
|
||||
|
||||
const Section = <T,>({
|
||||
section,
|
||||
children,
|
||||
urlKey,
|
||||
dateKey,
|
||||
summaryKey,
|
||||
keywordsKey,
|
||||
}: SectionProps<T>) => {
|
||||
if (!section.visible || !section.items.length) return null;
|
||||
|
||||
return (
|
||||
<section id={section.id} className={cn("grid", dateKey !== undefined && "gap-y-4")}>
|
||||
<div className="grid grid-cols-4 gap-x-6">
|
||||
<div className="text-right">
|
||||
<h4 className="font-medium text-primary">{section.name}</h4>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<div className="relative">
|
||||
<hr className="mt-3 border-primary" />
|
||||
<div className="absolute bottom-0 right-0 h-3 w-3 bg-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{dateKey !== undefined && (
|
||||
<div className="grid grid-cols-4 gap-x-6 gap-y-4">
|
||||
{section.items
|
||||
.filter((item) => item.visible)
|
||||
.map((item) => {
|
||||
const url = (urlKey && get(item, urlKey)) as URL | undefined;
|
||||
const date = (dateKey && get(item, dateKey, "")) as string | undefined;
|
||||
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
|
||||
const keywords = (keywordsKey && get(item, keywordsKey, [])) as string[] | undefined;
|
||||
|
||||
return (
|
||||
<Fragment key={item.id}>
|
||||
<div className="text-right font-medium text-primary">{date}</div>
|
||||
|
||||
<div className="col-span-3 space-y-1">
|
||||
{children?.(item as T)}
|
||||
|
||||
{url !== undefined && <Link url={url} />}
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dateKey === undefined && (
|
||||
<div className="grid grid-cols-4 gap-x-6">
|
||||
<div
|
||||
className="col-span-3 col-start-2 grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
.filter((item) => item.visible)
|
||||
.map((item) => {
|
||||
const url = (urlKey && get(item, urlKey)) as URL | undefined;
|
||||
const summary = (summaryKey && get(item, summaryKey, "")) as string | undefined;
|
||||
const keywords = (keywordsKey && get(item, keywordsKey, [])) as
|
||||
| string[]
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
{children?.(item as T)}
|
||||
|
||||
{url !== undefined && <Link url={url} />}
|
||||
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const Profiles = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.profiles);
|
||||
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
|
||||
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
label={item.username}
|
||||
icon={
|
||||
<img
|
||||
className="ph"
|
||||
width={fontSize}
|
||||
height={fontSize}
|
||||
alt={item.network}
|
||||
src={`https://cdn.simpleicons.org/${item.icon}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<p>{item.username}</p>
|
||||
)}
|
||||
<p className="text-sm">{item.network}</p>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Experience = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.experience);
|
||||
|
||||
return (
|
||||
<Section<Experience> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.company}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Education = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.education);
|
||||
|
||||
return (
|
||||
<Section<Education> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.institution}</div>
|
||||
<div>{item.area}</div>
|
||||
<div>{item.studyType}</div>
|
||||
<div>{item.score}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Awards = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.awards);
|
||||
|
||||
return (
|
||||
<Section<Award> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.title}</div>
|
||||
<div>{item.awarder}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Certifications = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.certifications);
|
||||
|
||||
return (
|
||||
<Section<Certification> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.issuer}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Skills = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.skills);
|
||||
|
||||
return (
|
||||
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Interests = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.interests);
|
||||
|
||||
return (
|
||||
<Section<Interest> section={section} keywordsKey="keywords">
|
||||
{(item) => <div className="font-bold">{item.name}</div>}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Publications = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.publications);
|
||||
|
||||
return (
|
||||
<Section<Publication> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.publisher}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Volunteer = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.volunteer);
|
||||
|
||||
return (
|
||||
<Section<Volunteer> section={section} urlKey="url" dateKey="date" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.organization}</div>
|
||||
<div>{item.position}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Projects = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.projects);
|
||||
|
||||
return (
|
||||
<Section<Project>
|
||||
section={section}
|
||||
urlKey="url"
|
||||
dateKey="date"
|
||||
summaryKey="summary"
|
||||
keywordsKey="keywords"
|
||||
>
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const References = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.references);
|
||||
|
||||
return (
|
||||
<Section<Reference> section={section} urlKey="url" summaryKey="summary">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Custom = ({ id }: { id: string }) => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.custom[id]);
|
||||
|
||||
return (
|
||||
<Section<CustomSection>
|
||||
section={section}
|
||||
urlKey="url"
|
||||
dateKey="date"
|
||||
summaryKey="summary"
|
||||
keywordsKey="keywords"
|
||||
>
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
<div>{item.location}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const mapSectionToComponent = (section: SectionKey) => {
|
||||
switch (section) {
|
||||
case "profiles":
|
||||
return <Profiles />;
|
||||
case "summary":
|
||||
return <Summary />;
|
||||
case "experience":
|
||||
return <Experience />;
|
||||
case "education":
|
||||
return <Education />;
|
||||
case "awards":
|
||||
return <Awards />;
|
||||
case "certifications":
|
||||
return <Certifications />;
|
||||
case "skills":
|
||||
return <Skills />;
|
||||
case "interests":
|
||||
return <Interests />;
|
||||
case "publications":
|
||||
return <Publications />;
|
||||
case "volunteer":
|
||||
return <Volunteer />;
|
||||
case "languages":
|
||||
return <Languages />;
|
||||
case "projects":
|
||||
return <Projects />;
|
||||
case "references":
|
||||
return <References />;
|
||||
default:
|
||||
if (section.startsWith("custom.")) return <Custom id={section.split(".")[1]} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const Nosepass = ({ columns, isFirstPage = false }: TemplateProps) => {
|
||||
const name = useArtboardStore((state) => state.resume.basics.name);
|
||||
|
||||
const [main, sidebar] = columns;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<img alt="Europass Logo" className="h-[48px]" src="https://i.imgur.com/eRK005p.png" />
|
||||
|
||||
<p className="font-medium text-primary">Curriculum Vitae</p>
|
||||
|
||||
<p className="font-medium text-primary">{name}</p>
|
||||
</div>
|
||||
|
||||
{isFirstPage && <Header />}
|
||||
|
||||
<div className="space-y-4">
|
||||
{main.map((section) => (
|
||||
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
|
||||
))}
|
||||
|
||||
{sidebar.map((section) => (
|
||||
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -110,7 +110,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="font-bold uppercase text-primary">{section.name}</h4>
|
||||
<h4 className="font-bold text-primary">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -182,10 +182,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="font-bold uppercase text-primary">{section.name}</h4>
|
||||
<h4 className="font-bold text-primary">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -198,18 +198,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -379,11 +379,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -34,8 +34,8 @@ const Header = () => {
|
||||
className="summary group bg-primary px-6 pb-7 pt-6 text-background"
|
||||
style={{ borderRadius: `calc(${borderRadius}px - 2px)` }}
|
||||
>
|
||||
<div className="col-span-2 space-y-4">
|
||||
<div className="leading-tight">
|
||||
<div className="col-span-2 space-y-2.5">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold">{basics.name}</h2>
|
||||
<p>{basics.headline}</p>
|
||||
</div>
|
||||
@ -102,7 +102,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -182,10 +182,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -198,18 +198,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -227,7 +227,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -358,7 +358,7 @@ const Interests = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.interests);
|
||||
|
||||
return (
|
||||
<Section<Interest> section={section} className="space-y-0" keywordsKey="keywords">
|
||||
<Section<Interest> section={section} className="space-y-1" keywordsKey="keywords">
|
||||
{(item) => <div className="font-bold">{item.name}</div>}
|
||||
</Section>
|
||||
);
|
||||
@ -411,11 +411,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@ -82,7 +82,7 @@ const Summary = () => {
|
||||
|
||||
return (
|
||||
<section id={section.id}>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="wysiwyg"
|
||||
@ -154,10 +154,10 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<section id={section.id} className="grid">
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold uppercase">{section.name}</h4>
|
||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||
|
||||
<div
|
||||
className="grid gap-3"
|
||||
className="grid gap-x-6 gap-y-3"
|
||||
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
|
||||
>
|
||||
{section.items
|
||||
@ -170,18 +170,18 @@ const Section = <T,>({
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", className)}>
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{children?.(item as T)}
|
||||
{url && <Link url={url} />}
|
||||
{url !== undefined && <Link url={url} />}
|
||||
</div>
|
||||
|
||||
{summary && !isEmptyString(summary) && (
|
||||
{summary !== undefined && !isEmptyString(summary) && (
|
||||
<div className="wysiwyg" dangerouslySetInnerHTML={{ __html: summary }} />
|
||||
)}
|
||||
|
||||
{level && level > 0 && <Rating level={level} />}
|
||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||
|
||||
{keywords && keywords.length > 0 && (
|
||||
{keywords !== undefined && keywords.length > 0 && (
|
||||
<p className="text-sm">{keywords.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
@ -199,7 +199,7 @@ const Profiles = () => {
|
||||
return (
|
||||
<Section<Profile> section={section}>
|
||||
{(item) => (
|
||||
<div className="leading-snug">
|
||||
<div>
|
||||
{isUrl(item.url.href) ? (
|
||||
<Link
|
||||
url={item.url}
|
||||
@ -317,7 +317,7 @@ const Skills = () => {
|
||||
return (
|
||||
<Section<Skill> section={section} levelKey="level" keywordsKey="keywords">
|
||||
{(item) => (
|
||||
<div className="leading-tight">
|
||||
<div>
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
@ -383,11 +383,11 @@ const Languages = () => {
|
||||
const section = useArtboardStore((state) => state.resume.sections.languages);
|
||||
|
||||
return (
|
||||
<Section<Language> section={section} levelKey="fluencyLevel">
|
||||
<Section<Language> section={section} levelKey="level">
|
||||
{(item) => (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-bold">{item.name}</div>
|
||||
<div>{item.fluency}</div>
|
||||
<div>{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
BIN
apps/client/public/templates/jpg/azurill.jpg
Executable file
|
After Width: | Height: | Size: 129 KiB |
BIN
apps/client/public/templates/jpg/bronzor.jpg
Executable file
|
After Width: | Height: | Size: 120 KiB |
BIN
apps/client/public/templates/jpg/chikorita.jpg
Executable file
|
After Width: | Height: | Size: 145 KiB |
BIN
apps/client/public/templates/jpg/ditto.jpg
Executable file
|
After Width: | Height: | Size: 138 KiB |
BIN
apps/client/public/templates/jpg/kakuna.jpg
Executable file
|
After Width: | Height: | Size: 119 KiB |
BIN
apps/client/public/templates/jpg/nosepass.jpg
Executable file
|
After Width: | Height: | Size: 99 KiB |
BIN
apps/client/public/templates/jpg/onyx.jpg
Executable file
|
After Width: | Height: | Size: 113 KiB |
BIN
apps/client/public/templates/jpg/pikachu.jpg
Executable file
|
After Width: | Height: | Size: 136 KiB |
BIN
apps/client/public/templates/jpg/rhyhorn.jpg
Executable file
|
After Width: | Height: | Size: 131 KiB |
321
apps/client/public/templates/json/azurill.json
Normal file
@ -0,0 +1,321 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 120,
|
||||
"aspectRatio": 1.2,
|
||||
"borderRadius": 4,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": []
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": false,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "azurill",
|
||||
"layout": [
|
||||
[
|
||||
["summary", "experience", "education", "references"],
|
||||
[
|
||||
"profiles",
|
||||
"skills",
|
||||
"certifications",
|
||||
"projects",
|
||||
"interests",
|
||||
"languages",
|
||||
"awards",
|
||||
"volunteer",
|
||||
"publications"
|
||||
]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#ca8a04"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Merriweather",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.75,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
346
apps/client/public/templates/json/bronzor.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 140,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 4,
|
||||
"effects": {
|
||||
"hidden": true,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": [
|
||||
{
|
||||
"id": "ey0c353j3apepzqmyl8zd37c",
|
||||
"visible": true,
|
||||
"name": "English",
|
||||
"description": "Native Speaker",
|
||||
"level": 0
|
||||
},
|
||||
{
|
||||
"id": "a4dnf32wyj5phgln2rx97qk2",
|
||||
"visible": true,
|
||||
"name": "Spanish",
|
||||
"description": "Intermediate",
|
||||
"level": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "k97vdxzmssmgel79km48kxgg",
|
||||
"visible": true,
|
||||
"network": "StackOverflow",
|
||||
"username": "johndoe",
|
||||
"icon": "stackoverflow",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://stackoverflow.com/u/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "bronzor",
|
||||
"layout": [
|
||||
[
|
||||
["profiles", "summary", "experience", "education", "projects"],
|
||||
[
|
||||
"skills",
|
||||
"certifications",
|
||||
"languages",
|
||||
"references",
|
||||
"interests",
|
||||
"awards",
|
||||
"volunteer",
|
||||
"publications"
|
||||
]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#059669"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "IBM Plex Sans",
|
||||
"subset": "latin",
|
||||
"variants": ["regular", "500"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.5,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
346
apps/client/public/templates/json/chikorita.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 140,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 4,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": [
|
||||
{
|
||||
"id": "ey0c353j3apepzqmyl8zd37c",
|
||||
"visible": true,
|
||||
"name": "English",
|
||||
"description": "Native Speaker",
|
||||
"level": 0
|
||||
},
|
||||
{
|
||||
"id": "a4dnf32wyj5phgln2rx97qk2",
|
||||
"visible": true,
|
||||
"name": "Spanish",
|
||||
"description": "Intermediate",
|
||||
"level": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "k97vdxzmssmgel79km48kxgg",
|
||||
"visible": true,
|
||||
"network": "StackOverflow",
|
||||
"username": "johndoe",
|
||||
"icon": "stackoverflow",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://stackoverflow.com/u/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "chikorita",
|
||||
"layout": [
|
||||
[
|
||||
["profiles", "summary", "experience", "education", "projects"],
|
||||
[
|
||||
"skills",
|
||||
"certifications",
|
||||
"languages",
|
||||
"references",
|
||||
"interests",
|
||||
"awards",
|
||||
"volunteer",
|
||||
"publications"
|
||||
]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#059669"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Merriweather",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.5,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
347
apps/client/public/templates/json/ditto.json
Normal file
@ -0,0 +1,347 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 140,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 4,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": [
|
||||
{
|
||||
"id": "ey0c353j3apepzqmyl8zd37c",
|
||||
"visible": true,
|
||||
"name": "English",
|
||||
"description": "Native Speaker",
|
||||
"level": 0
|
||||
},
|
||||
{
|
||||
"id": "a4dnf32wyj5phgln2rx97qk2",
|
||||
"visible": true,
|
||||
"name": "Spanish",
|
||||
"description": "Intermediate",
|
||||
"level": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "k97vdxzmssmgel79km48kxgg",
|
||||
"visible": true,
|
||||
"network": "StackOverflow",
|
||||
"username": "johndoe",
|
||||
"icon": "stackoverflow",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://stackoverflow.com/u/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "ditto",
|
||||
"layout": [
|
||||
[
|
||||
["summary", "experience", "education", "projects"],
|
||||
[
|
||||
"profiles",
|
||||
"skills",
|
||||
"certifications",
|
||||
"languages",
|
||||
"references",
|
||||
"interests",
|
||||
"awards",
|
||||
"volunteer",
|
||||
"publications"
|
||||
]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#0891b2"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Merriweather",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.75,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
319
apps/client/public/templates/json/kakuna.json
Normal file
@ -0,0 +1,319 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 64,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 0,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": []
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "kakuna",
|
||||
"layout": [
|
||||
[
|
||||
[
|
||||
"summary",
|
||||
"experience",
|
||||
"education",
|
||||
"projects",
|
||||
"certifications",
|
||||
"skills",
|
||||
"references"
|
||||
],
|
||||
["profiles", "interests", "languages", "awards", "volunteer", "publications"]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#57534e"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Lora",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.5,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
338
apps/client/public/templates/json/nosepass.json
Normal file
@ -0,0 +1,338 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 90,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 4,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "Aug 2012 - May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "Jan 2019 - Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "Jun 2016 - Dec 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": [
|
||||
{
|
||||
"id": "ey0c353j3apepzqmyl8zd37c",
|
||||
"visible": true,
|
||||
"name": "English",
|
||||
"description": "Native Speaker",
|
||||
"level": 0
|
||||
},
|
||||
{
|
||||
"id": "a4dnf32wyj5phgln2rx97qk2",
|
||||
"visible": true,
|
||||
"name": "Spanish",
|
||||
"description": "Intermediate",
|
||||
"level": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "k97vdxzmssmgel79km48kxgg",
|
||||
"visible": true,
|
||||
"network": "StackOverflow",
|
||||
"username": "johndoe",
|
||||
"icon": "stackoverflow",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://stackoverflow.com/u/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "nosepass",
|
||||
"layout": [
|
||||
[
|
||||
["profiles", "summary", "experience", "education"],
|
||||
["interests", "awards", "volunteer", "publications"]
|
||||
],
|
||||
[["projects", "certifications", "skills", "languages", "references"], []]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 24,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#5d93bc"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Work Sans",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13.8
|
||||
},
|
||||
"lineHeight": 1.5,
|
||||
"hideIcons": true,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
319
apps/client/public/templates/json/onyx.json
Normal file
@ -0,0 +1,319 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 100,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 0,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor of Science",
|
||||
"area": "Computer Science",
|
||||
"score": "Berkeley, CA",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://www.universityofcalifornia.edu/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": []
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "onyx",
|
||||
"layout": [
|
||||
[
|
||||
[
|
||||
"summary",
|
||||
"experience",
|
||||
"education",
|
||||
"projects",
|
||||
"certifications",
|
||||
"skills",
|
||||
"references"
|
||||
],
|
||||
["profiles", "interests", "languages", "awards", "volunteer", "publications"]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 18,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#dc2626"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "PT Serif",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.5,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": false
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
347
apps/client/public/templates/json/pikachu.json
Normal file
@ -0,0 +1,347 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 140,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 4,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": [
|
||||
{
|
||||
"id": "ey0c353j3apepzqmyl8zd37c",
|
||||
"visible": true,
|
||||
"name": "English",
|
||||
"description": "Native Speaker",
|
||||
"level": 0
|
||||
},
|
||||
{
|
||||
"id": "a4dnf32wyj5phgln2rx97qk2",
|
||||
"visible": true,
|
||||
"name": "Spanish",
|
||||
"description": "Intermediate",
|
||||
"level": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "k97vdxzmssmgel79km48kxgg",
|
||||
"visible": true,
|
||||
"network": "StackOverflow",
|
||||
"username": "johndoe",
|
||||
"icon": "stackoverflow",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://stackoverflow.com/u/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "pikachu",
|
||||
"layout": [
|
||||
[
|
||||
["summary", "experience", "education", "projects"],
|
||||
[
|
||||
"profiles",
|
||||
"skills",
|
||||
"certifications",
|
||||
"languages",
|
||||
"references",
|
||||
"interests",
|
||||
"awards",
|
||||
"volunteer",
|
||||
"publications"
|
||||
]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#ca8a04"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Titillium Web",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13.8
|
||||
},
|
||||
"lineHeight": 1.5,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": false
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
320
apps/client/public/templates/json/rhyhorn.json
Normal file
@ -0,0 +1,320 @@
|
||||
{
|
||||
"basics": {
|
||||
"name": "John Doe",
|
||||
"headline": "Creative and Innovative Web Developer",
|
||||
"email": "john.doe@gmail.com",
|
||||
"phone": "(555) 123-4567",
|
||||
"location": "Pleasantville, CA 94588",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://johndoe.me/"
|
||||
},
|
||||
"customFields": [],
|
||||
"picture": {
|
||||
"url": "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
"size": 64,
|
||||
"aspectRatio": 1,
|
||||
"borderRadius": 0,
|
||||
"effects": {
|
||||
"hidden": false,
|
||||
"border": false,
|
||||
"grayscale": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"summary": {
|
||||
"name": "Summary",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "summary",
|
||||
"content": "<p>Innovative Web Developer with 5 years of experience in building impactful and user-friendly websites and applications. Specializes in <strong>front-end technologies</strong> and passionate about modern web standards and cutting-edge development techniques. Proven track record of leading successful projects from concept to deployment.</p>"
|
||||
},
|
||||
"awards": {
|
||||
"name": "Awards",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "awards",
|
||||
"items": []
|
||||
},
|
||||
"certifications": {
|
||||
"name": "Certifications",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "certifications",
|
||||
"items": [
|
||||
{
|
||||
"id": "spdhh9rrqi1gvj0yqnbqunlo",
|
||||
"visible": true,
|
||||
"name": "Full-Stack Web Development",
|
||||
"issuer": "CodeAcademy",
|
||||
"date": "2020",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n838rddyqv47zexn6cxauwqp",
|
||||
"visible": true,
|
||||
"name": "AWS Certified Developer",
|
||||
"issuer": "Amazon Web Services",
|
||||
"date": "2019",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"education": {
|
||||
"name": "Education",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "education",
|
||||
"items": [
|
||||
{
|
||||
"id": "yo3p200zo45c6cdqc6a2vtt3",
|
||||
"visible": true,
|
||||
"institution": "University of California",
|
||||
"studyType": "Bachelor's in Computer Science",
|
||||
"area": "Berkeley, CA",
|
||||
"score": "",
|
||||
"date": "August 2012 to May 2016",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"experience": {
|
||||
"name": "Experience",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "experience",
|
||||
"items": [
|
||||
{
|
||||
"id": "lhw25d7gf32wgdfpsktf6e0x",
|
||||
"visible": true,
|
||||
"company": "Creative Solutions Inc.",
|
||||
"position": "Senior Web Developer",
|
||||
"location": "San Francisco, CA",
|
||||
"date": "January 2019 to Present",
|
||||
"summary": "<ul><li><p>Spearheaded the redesign of the main product website, resulting in a 40% increase in user engagement.</p></li><li><p>Developed and implemented a new responsive framework, improving cross-device compatibility.</p></li><li><p>Mentored a team of four junior developers, fostering a culture of technical excellence.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://creativesolutions.inc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "r6543lil53ntrxmvel53gbtm",
|
||||
"visible": true,
|
||||
"company": "TechAdvancers",
|
||||
"position": "Web Developer",
|
||||
"location": "San Jose, CA",
|
||||
"date": "June 2016 to December 2018",
|
||||
"summary": "<ul><li><p>Collaborated in a team of 10 to develop high-quality web applications using React.js and Node.js.</p></li><li><p>Managed the integration of third-party services such as Stripe for payments and Twilio for SMS services.</p></li><li><p>Optimized application performance, achieving a 30% reduction in load times.</p></li></ul>",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://techadvancers.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"volunteer": {
|
||||
"name": "Volunteering",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "volunteer",
|
||||
"items": []
|
||||
},
|
||||
"interests": {
|
||||
"name": "Interests",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "interests",
|
||||
"items": []
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "languages",
|
||||
"items": []
|
||||
},
|
||||
"profiles": {
|
||||
"name": "Profiles",
|
||||
"columns": 4,
|
||||
"visible": true,
|
||||
"id": "profiles",
|
||||
"items": [
|
||||
{
|
||||
"id": "cnbk5f0aeqvhx69ebk7hktwd",
|
||||
"visible": true,
|
||||
"network": "LinkedIn",
|
||||
"username": "johndoe",
|
||||
"icon": "linkedin",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://linkedin.com/in/johndoe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ukl0uecvzkgm27mlye0wazlb",
|
||||
"visible": true,
|
||||
"network": "GitHub",
|
||||
"username": "johndoe",
|
||||
"icon": "github",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": "https://github.com/johndoe"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"name": "Projects",
|
||||
"columns": 2,
|
||||
"visible": true,
|
||||
"id": "projects",
|
||||
"items": [
|
||||
{
|
||||
"id": "yw843emozcth8s1ubi1ubvlf",
|
||||
"visible": true,
|
||||
"name": "E-Commerce Platform",
|
||||
"description": "Project Lead",
|
||||
"date": "",
|
||||
"summary": "<p>Led the development of a full-stack e-commerce platform, improving sales conversion by 25%.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ncxgdjjky54gh59iz2t1xi1v",
|
||||
"visible": true,
|
||||
"name": "Interactive Dashboard",
|
||||
"description": "Frontend Developer",
|
||||
"date": "",
|
||||
"summary": "<p>Created an interactive analytics dashboard for a SaaS application, enhancing data visualization for clients.</p>",
|
||||
"keywords": [],
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"publications": {
|
||||
"name": "Publications",
|
||||
"columns": 1,
|
||||
"visible": true,
|
||||
"id": "publications",
|
||||
"items": []
|
||||
},
|
||||
"references": {
|
||||
"name": "References",
|
||||
"columns": 1,
|
||||
"visible": false,
|
||||
"id": "references",
|
||||
"items": [
|
||||
{
|
||||
"id": "f2sv5z0cce6ztjl87yuk8fak",
|
||||
"visible": true,
|
||||
"name": "Available upon request",
|
||||
"description": "",
|
||||
"summary": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"href": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"skills": {
|
||||
"name": "Skills",
|
||||
"columns": 3,
|
||||
"visible": true,
|
||||
"id": "skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "hn0keriukh6c0ojktl9gsgjm",
|
||||
"visible": true,
|
||||
"name": "Web Technologies",
|
||||
"description": "Advanced",
|
||||
"level": 0,
|
||||
"keywords": ["HTML5", "JavaScript", "PHP", "Python"]
|
||||
},
|
||||
{
|
||||
"id": "r8c3y47vykausqrgmzwg5pur",
|
||||
"visible": true,
|
||||
"name": "Web Frameworks",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["React.js", "Angular", "Vue.js", "Laravel", "Django"]
|
||||
},
|
||||
{
|
||||
"id": "b5l75aseexqv17quvqgh73fe",
|
||||
"visible": true,
|
||||
"name": "Tools",
|
||||
"description": "Intermediate",
|
||||
"level": 0,
|
||||
"keywords": ["Webpack", "Git", "Jenkins", "Docker", "JIRA"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"custom": {}
|
||||
},
|
||||
"metadata": {
|
||||
"template": "rhyhorn",
|
||||
"layout": [
|
||||
[
|
||||
[
|
||||
"profiles",
|
||||
"summary",
|
||||
"experience",
|
||||
"education",
|
||||
"projects",
|
||||
"certifications",
|
||||
"skills",
|
||||
"references"
|
||||
],
|
||||
["interests", "languages", "awards", "volunteer", "publications"]
|
||||
]
|
||||
],
|
||||
"css": {
|
||||
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
|
||||
"visible": false
|
||||
},
|
||||
"page": {
|
||||
"margin": 14,
|
||||
"format": "a4",
|
||||
"options": {
|
||||
"breakLine": true,
|
||||
"pageNumbers": true
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"background": "#ffffff",
|
||||
"text": "#000000",
|
||||
"primary": "#ca8a04"
|
||||
},
|
||||
"typography": {
|
||||
"font": {
|
||||
"family": "Merriweather",
|
||||
"subset": "latin",
|
||||
"variants": ["regular"],
|
||||
"size": 13
|
||||
},
|
||||
"lineHeight": 1.75,
|
||||
"hideIcons": false,
|
||||
"underlineLinks": true
|
||||
},
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
BIN
apps/client/public/templates/pdf/azurill.pdf
Normal file
BIN
apps/client/public/templates/pdf/bronzor.pdf
Normal file
BIN
apps/client/public/templates/pdf/chikorita.pdf
Normal file
BIN
apps/client/public/templates/pdf/ditto.pdf
Normal file
BIN
apps/client/public/templates/pdf/kakuna.pdf
Normal file
BIN
apps/client/public/templates/pdf/nosepass.pdf
Normal file
BIN
apps/client/public/templates/pdf/onyx.pdf
Normal file
BIN
apps/client/public/templates/pdf/pikachu.pdf
Normal file
BIN
apps/client/public/templates/pdf/rhyhorn.pdf
Normal file
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1,20 +1,21 @@
|
||||
export const colors: string[] = [
|
||||
"#78716c", // stone-500
|
||||
"#ef4444", // red-500
|
||||
"#f97316", // orange-500
|
||||
"#f59e0b", // amber-500
|
||||
"#eab308", // yellow-500
|
||||
"#84cc16", // lime-500
|
||||
"#22c55e", // green-500
|
||||
"#10b981", // emerald-500
|
||||
"#14b8a6", // teal-500
|
||||
"#06b6d4", // cyan-500
|
||||
"#0ea5e9", // sky-500
|
||||
"#3b82f6", // blue-500
|
||||
"#6366f1", // indigo-500
|
||||
"#8b5cf6", // violet-500
|
||||
"#a855f7", // purple-500
|
||||
"#d946ef", // fuchsia-500
|
||||
"#ec4899", // pink-500
|
||||
"#f43f5e", // rose-500
|
||||
"#475569", // slate-600
|
||||
"#57534e", // stone-600
|
||||
"#dc2626", // red-600
|
||||
"#ea580c", // orange-600
|
||||
"#d97706", // amber-600
|
||||
"#ca8a04", // yellow-600
|
||||
"#65a30d", // lime-600
|
||||
"#16a34a", // green-600
|
||||
"#059669", // emerald-600
|
||||
"#0d9488", // teal-600
|
||||
"#0891b2", // cyan-600
|
||||
"#0284c7", // sky-600
|
||||
"#2563eb", // blue-600
|
||||
"#4f46e5", // indigo-600
|
||||
"#7c3aed", // violet-600
|
||||
"#9333ea", // purple-600
|
||||
"#c026d3", // fuchsia-600
|
||||
"#db2777", // pink-600
|
||||
"#e11d48", // rose-600
|
||||
];
|
||||
|
||||
@ -210,7 +210,7 @@ msgid "Backup Codes may contain only lowercase letters or numbers, and must be e
|
||||
msgstr "Backup Codes may contain only lowercase letters or numbers, and must be exactly 10 characters."
|
||||
|
||||
#: apps/client/src/pages/builder/sidebars/left/index.tsx:57
|
||||
msgctxt "The Basics section of a Resume consists of User's Picture, Full Name, Location etc."
|
||||
msgctxt "The basics section of a resume consists of User's Picture, Full Name, Location etc."
|
||||
msgid "Basics"
|
||||
msgstr "Basics"
|
||||
|
||||
@ -247,7 +247,7 @@ msgstr "Cancel"
|
||||
msgid "Casual"
|
||||
msgstr "Casual"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:90
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:89
|
||||
msgid "Center Artboard"
|
||||
msgstr "Center Artboard"
|
||||
|
||||
@ -310,7 +310,7 @@ msgstr "Continue"
|
||||
msgid "Copy"
|
||||
msgstr "Copy"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:124
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:123
|
||||
msgid "Copy Link to Resume"
|
||||
msgstr "Copy Link to Resume"
|
||||
|
||||
@ -521,7 +521,7 @@ msgstr "Even if you're not in a position to contribute financially, you can stil
|
||||
msgid "Export"
|
||||
msgstr "Export"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:130
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:129
|
||||
msgid "Export as PDF"
|
||||
msgstr "Export as PDF"
|
||||
|
||||
@ -652,6 +652,8 @@ msgstr "Here, you can update your account information such as your profile pictu
|
||||
msgid "Here, you can update your profile to customize and personalize your experience."
|
||||
msgstr "Here, you can update your profile to customize and personalize your experience."
|
||||
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/languages.tsx:77
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:82
|
||||
#: apps/client/src/pages/builder/sidebars/left/sections/picture/options.tsx:185
|
||||
msgid "Hidden"
|
||||
msgstr "Hidden"
|
||||
@ -787,7 +789,7 @@ msgstr "JSON"
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:145
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/interests.tsx:55
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:122
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:95
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:99
|
||||
msgid "Keywords"
|
||||
msgstr "Keywords"
|
||||
|
||||
@ -952,7 +954,7 @@ msgstr "Notes"
|
||||
msgid "One-Time Password"
|
||||
msgstr "One-Time Password"
|
||||
|
||||
#: apps/client/src/libs/axios.ts:32
|
||||
#: apps/client/src/libs/axios.ts:34
|
||||
#: apps/client/src/pages/dashboard/resumes/_dialogs/import.tsx:190
|
||||
msgid "Oops, the server returned an error."
|
||||
msgstr "Oops, the server returned an error."
|
||||
@ -1028,7 +1030,7 @@ msgstr "Photograph by Patrick Tomasso"
|
||||
msgid "Pick any font from Google Fonts"
|
||||
msgstr "Pick any font from Google Fonts"
|
||||
|
||||
#: apps/client/src/pages/builder/sidebars/left/sections/picture/section.tsx:52
|
||||
#: apps/client/src/pages/builder/sidebars/left/sections/picture/section.tsx:69
|
||||
#: apps/client/src/pages/dashboard/settings/_sections/account.tsx:120
|
||||
msgid "Picture"
|
||||
msgstr "Picture"
|
||||
@ -1118,7 +1120,7 @@ msgstr "Reactive Resume is a passion project of over 3 years of hard work, and w
|
||||
msgid "Reactive Resume thrives thanks to its vibrant community. This project owes its progress to numerous individuals who've dedicated their time and skills. Below, we celebrate the coders who've enhanced its features on GitHub and the linguists whose translations on Crowdin have made it accessible to a broader audience."
|
||||
msgstr "Reactive Resume thrives thanks to its vibrant community. This project owes its progress to numerous individuals who've dedicated their time and skills. Below, we celebrate the coders who've enhanced its features on GitHub and the linguists whose translations on Crowdin have made it accessible to a broader audience."
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:64
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:63
|
||||
msgid "Redo"
|
||||
msgstr "Redo"
|
||||
|
||||
@ -1150,7 +1152,7 @@ msgstr "Reset Layout"
|
||||
msgid "Reset your password"
|
||||
msgstr "Reset your password"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:84
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:83
|
||||
msgid "Reset Zoom"
|
||||
msgstr "Reset Zoom"
|
||||
|
||||
@ -1444,11 +1446,11 @@ msgstr "Title"
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:98
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:97
|
||||
msgid "Toggle Page Break Line"
|
||||
msgstr "Toggle Page Break Line"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:110
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:109
|
||||
msgid "Toggle Page Numbers"
|
||||
msgstr "Toggle Page Numbers"
|
||||
|
||||
@ -1487,7 +1489,7 @@ msgstr "Typography"
|
||||
msgid "Underline Links"
|
||||
msgstr "Underline Links"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:58
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:57
|
||||
msgid "Undo"
|
||||
msgstr "Undo"
|
||||
|
||||
@ -1603,7 +1605,7 @@ msgstr "What's new in the latest version"
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/custom-section.tsx:150
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/interests.tsx:60
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/projects.tsx:127
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:100
|
||||
#: apps/client/src/pages/builder/sidebars/left/dialogs/skills.tsx:104
|
||||
msgid "You can add multiple keywords by separating them with a comma or pressing enter."
|
||||
msgstr "You can add multiple keywords by separating them with a comma or pressing enter."
|
||||
|
||||
@ -1651,10 +1653,10 @@ msgstr "Your OpenAI API Key has not been set yet. Please go to your account sett
|
||||
msgid "Your password has been updated successfully."
|
||||
msgstr "Your password has been updated successfully."
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:72
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:71
|
||||
msgid "Zoom In"
|
||||
msgstr "Zoom In"
|
||||
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:78
|
||||
#: apps/client/src/pages/builder/_components/toolbar.tsx:77
|
||||
msgid "Zoom Out"
|
||||
msgstr "Zoom Out"
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
CircleNotch,
|
||||
ClockClockwise,
|
||||
CubeFocus,
|
||||
DownloadSimple,
|
||||
FilePdf,
|
||||
Hash,
|
||||
LineSegment,
|
||||
LinkSimple,
|
||||
@ -51,7 +51,7 @@ export const BuilderToolbar = () => {
|
||||
<motion.div
|
||||
initial={{ opacity: 0.5 }}
|
||||
whileHover={{ opacity: 1 }}
|
||||
className="fixed inset-x-0 mx-auto hidden pb-4 pt-6 text-center md:block"
|
||||
className="fixed inset-x-0 bottom-0 mx-auto pb-4 pt-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`}>
|
||||
@ -134,7 +134,7 @@ export const BuilderToolbar = () => {
|
||||
onClick={onPrint}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? <CircleNotch className="animate-spin" /> : <DownloadSimple />}
|
||||
{loading ? <CircleNotch className="animate-spin" /> : <FilePdf />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@ -10,7 +10,6 @@ import {
|
||||
Input,
|
||||
Slider,
|
||||
} from "@reactive-resume/ui";
|
||||
import { getCEFRLevel } from "@reactive-resume/utils";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
@ -44,11 +43,11 @@ export const LanguagesDialog = () => {
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="fluency"
|
||||
name="description"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t`Fluency`}</FormLabel>
|
||||
<FormLabel>{t`Description`}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@ -58,22 +57,26 @@ export const LanguagesDialog = () => {
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="fluencyLevel"
|
||||
name="level"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="sm:col-span-2">
|
||||
<FormLabel>{t`Fluency (CEFR)`}</FormLabel>
|
||||
<FormLabel>{t`Level`}</FormLabel>
|
||||
<FormControl className="py-2">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Slider
|
||||
{...field}
|
||||
min={1}
|
||||
max={6}
|
||||
min={0}
|
||||
max={5}
|
||||
value={[field.value]}
|
||||
onValueChange={(value) => field.onChange(value[0])}
|
||||
/>
|
||||
|
||||
<span className="text-base font-bold">{getCEFRLevel(field.value)}</span>
|
||||
{field.value === 0 ? (
|
||||
<span className="text-base font-bold">{t`Hidden`}</span>
|
||||
) : (
|
||||
<span className="text-base font-bold">{field.value}</span>
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@ -71,14 +71,18 @@ export const SkillsDialog = () => {
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Slider
|
||||
{...field}
|
||||
min={1}
|
||||
min={0}
|
||||
max={5}
|
||||
value={[field.value]}
|
||||
orientation="horizontal"
|
||||
onValueChange={(value) => field.onChange(value[0])}
|
||||
/>
|
||||
|
||||
<span className="text-base font-bold">{field.value}</span>
|
||||
{field.value === 0 ? (
|
||||
<span className="text-base font-bold">{t`Hidden`}</span>
|
||||
) : (
|
||||
<span className="text-base font-bold">{field.value}</span>
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@ -16,7 +16,6 @@ import {
|
||||
Volunteer,
|
||||
} from "@reactive-resume/schema";
|
||||
import { Button, ScrollArea, Separator } from "@reactive-resume/ui";
|
||||
import { getCEFRLevel } from "@reactive-resume/utils";
|
||||
import { Fragment, useRef } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
@ -57,21 +56,21 @@ export const LeftSidebar = () => {
|
||||
name={t({
|
||||
message: "Basics",
|
||||
context:
|
||||
"The Basics section of a Resume consists of User's Picture, Full Name, Location etc.",
|
||||
"The basics section of a resume consists of User's Picture, Full Name, Location etc.",
|
||||
})}
|
||||
/>
|
||||
<SectionIcon id="summary" onClick={() => scrollIntoView("#summary")} />
|
||||
<SectionIcon id="profiles" onClick={() => scrollIntoView("#profiles")} />
|
||||
<SectionIcon id="experience" onClick={() => scrollIntoView("#experience")} />
|
||||
<SectionIcon id="education" onClick={() => scrollIntoView("#education")} />
|
||||
<SectionIcon id="skills" onClick={() => scrollIntoView("#skills")} />
|
||||
<SectionIcon id="languages" onClick={() => scrollIntoView("#languages")} />
|
||||
<SectionIcon id="awards" onClick={() => scrollIntoView("#awards")} />
|
||||
<SectionIcon id="certifications" onClick={() => scrollIntoView("#certifications")} />
|
||||
<SectionIcon id="interests" onClick={() => scrollIntoView("#interests")} />
|
||||
<SectionIcon id="languages" onClick={() => scrollIntoView("#languages")} />
|
||||
<SectionIcon id="volunteer" onClick={() => scrollIntoView("#volunteer")} />
|
||||
<SectionIcon id="projects" onClick={() => scrollIntoView("#projects")} />
|
||||
<SectionIcon id="publications" onClick={() => scrollIntoView("#publications")} />
|
||||
<SectionIcon id="skills" onClick={() => scrollIntoView("#skills")} />
|
||||
<SectionIcon id="volunteer" onClick={() => scrollIntoView("#volunteer")} />
|
||||
<SectionIcon id="references" onClick={() => scrollIntoView("#references")} />
|
||||
|
||||
<SectionIcon
|
||||
@ -118,6 +117,21 @@ export const LeftSidebar = () => {
|
||||
description={(item) => item.area}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Skill>
|
||||
id="skills"
|
||||
title={(item) => item.name}
|
||||
description={(item) => {
|
||||
if (item.description) return item.description;
|
||||
if (item.keywords.length > 0) return `${item.keywords.length} keywords`;
|
||||
}}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Language>
|
||||
id="languages"
|
||||
title={(item) => item.name}
|
||||
description={(item) => item.description}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Award>
|
||||
id="awards"
|
||||
title={(item) => item.title}
|
||||
@ -138,18 +152,6 @@ export const LeftSidebar = () => {
|
||||
}}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Language>
|
||||
id="languages"
|
||||
title={(item) => item.name}
|
||||
description={(item) => item.fluency || getCEFRLevel(item.fluencyLevel)}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Volunteer>
|
||||
id="volunteer"
|
||||
title={(item) => item.organization}
|
||||
description={(item) => item.position}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Project>
|
||||
id="projects"
|
||||
title={(item) => item.name}
|
||||
@ -162,13 +164,10 @@ export const LeftSidebar = () => {
|
||||
description={(item) => item.publisher}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Skill>
|
||||
id="skills"
|
||||
title={(item) => item.name}
|
||||
description={(item) => {
|
||||
if (item.description) return item.description;
|
||||
if (item.keywords.length > 0) return `${item.keywords.length} keywords`;
|
||||
}}
|
||||
<SectionBase<Volunteer>
|
||||
id="volunteer"
|
||||
title={(item) => item.organization}
|
||||
description={(item) => item.position}
|
||||
/>
|
||||
<Separator />
|
||||
<SectionBase<Reference>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { Aperture, UploadSimple } from "@phosphor-icons/react";
|
||||
import { Aperture, Trash, UploadSimple } from "@phosphor-icons/react";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
buttonVariants,
|
||||
Input,
|
||||
@ -11,8 +10,8 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@reactive-resume/ui";
|
||||
import { cn, getInitials } from "@reactive-resume/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { cn } from "@reactive-resume/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { useMemo, useRef } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
@ -23,10 +22,9 @@ import { PictureOptions } from "./options";
|
||||
|
||||
export const PictureSection = () => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { uploadImage, loading } = useUploadImage();
|
||||
const { uploadImage } = useUploadImage();
|
||||
|
||||
const setValue = useResumeStore((state) => state.setValue);
|
||||
const name = useResumeStore((state) => state.resume.data.basics.name);
|
||||
const picture = useResumeStore((state) => state.resume.data.basics.picture);
|
||||
|
||||
const isValidUrl = useMemo(() => z.string().url().safeParse(picture.url).success, [picture.url]);
|
||||
@ -41,16 +39,37 @@ export const PictureSection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onAvatarClick = () => {
|
||||
if (isValidUrl) {
|
||||
setValue("basics.picture.url", "");
|
||||
} else {
|
||||
inputRef.current?.click();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Avatar className="h-14 w-14">
|
||||
{isValidUrl && <AvatarImage src={picture.url} />}
|
||||
<AvatarFallback className="text-lg font-bold">{getInitials(name)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="group relative cursor-pointer" onClick={onAvatarClick}>
|
||||
<Avatar className="h-14 w-14 bg-secondary">
|
||||
<AvatarImage src={picture.url} />
|
||||
</Avatar>
|
||||
|
||||
{isValidUrl ? (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center rounded-full bg-background/30 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<Trash size={16} weight="bold" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center rounded-full bg-background/30 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<UploadSimple size={16} weight="bold" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col gap-y-1.5">
|
||||
<Label htmlFor="basics.picture.url">{t`Picture`}</Label>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<input hidden type="file" ref={inputRef} onChange={onSelectImage} />
|
||||
|
||||
<Input
|
||||
id="basics.picture.url"
|
||||
placeholder="https://..."
|
||||
@ -58,44 +77,23 @@ export const PictureSection = () => {
|
||||
onChange={(event) => setValue("basics.picture.url", event.target.value)}
|
||||
/>
|
||||
|
||||
<AnimatePresence>
|
||||
{/* Show options button if picture exists */}
|
||||
{isValidUrl && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<motion.button
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className={cn(buttonVariants({ size: "icon", variant: "ghost" }))}
|
||||
>
|
||||
<Aperture />
|
||||
</motion.button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[360px]">
|
||||
<PictureOptions />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
{/* Show upload button if picture doesn't exist, else show remove button to delete picture */}
|
||||
{!isValidUrl && (
|
||||
<>
|
||||
<input hidden type="file" ref={inputRef} onChange={onSelectImage} />
|
||||
|
||||
{isValidUrl && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<motion.button
|
||||
disabled={loading}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
className={cn(buttonVariants({ size: "icon", variant: "ghost" }))}
|
||||
>
|
||||
<UploadSimple />
|
||||
<Aperture />
|
||||
</motion.button>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[360px]">
|
||||
<PictureOptions />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { Button, HoverCard, HoverCardContent, HoverCardTrigger } from "@reactive-resume/ui";
|
||||
import { AspectRatio } from "@reactive-resume/ui";
|
||||
import { cn, templatesList } from "@reactive-resume/utils";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { useResumeStore } from "@/client/stores/resume";
|
||||
|
||||
@ -19,31 +20,28 @@ export const TemplateSection = () => {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="grid grid-cols-2 gap-4">
|
||||
{templatesList.map(({ id, name }) => (
|
||||
<HoverCard key={id} openDelay={0} closeDelay={0}>
|
||||
<HoverCardTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setValue("metadata.template", id)}
|
||||
className={cn(
|
||||
"flex h-12 items-center justify-center overflow-hidden rounded border text-center text-sm capitalize ring-primary transition-colors hover:bg-secondary-accent focus:outline-none focus:ring-1 disabled:opacity-100",
|
||||
id === currentTemplate && "ring-1",
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<main className="grid grid-cols-2 gap-5 @lg/right:grid-cols-3 @2xl/right:grid-cols-4">
|
||||
{templatesList.map((template, index) => (
|
||||
<AspectRatio ratio={1 / 1.4142}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0, transition: { delay: index * 0.1 } }}
|
||||
whileTap={{ scale: 0.98, transition: { duration: 0.1 } }}
|
||||
onClick={() => setValue("metadata.template", template)}
|
||||
className={cn(
|
||||
"relative cursor-pointer rounded-sm ring-primary transition-all hover:ring-2",
|
||||
currentTemplate === template && "ring-2",
|
||||
)}
|
||||
>
|
||||
<img src={`/templates/jpg/${template}.jpg`} alt={template} className="rounded-sm" />
|
||||
|
||||
<HoverCardContent className="max-w-xs overflow-hidden border-none bg-white p-0">
|
||||
<img
|
||||
alt={name}
|
||||
loading="lazy"
|
||||
src="/templates/sample.jpg"
|
||||
className="aspect-[1/1.4142]"
|
||||
/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
<div className="absolute inset-x-0 bottom-0 h-32 w-full bg-gradient-to-b from-transparent to-background/80">
|
||||
<p className="absolute inset-x-0 bottom-2 text-center font-bold capitalize text-primary">
|
||||
{template}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AspectRatio>
|
||||
))}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@ -137,7 +137,7 @@ export const TypographySection = () => {
|
||||
<Slider
|
||||
min={12}
|
||||
max={18}
|
||||
step={1}
|
||||
step={0.05}
|
||||
value={[typography.font.size]}
|
||||
onValueChange={(value) => {
|
||||
setValue("metadata.typography.font.size", value[0]);
|
||||
@ -154,7 +154,7 @@ export const TypographySection = () => {
|
||||
<Slider
|
||||
min={0}
|
||||
max={3}
|
||||
step={0.25}
|
||||
step={0.05}
|
||||
value={[typography.lineHeight]}
|
||||
onValueChange={(value) => {
|
||||
setValue("metadata.typography.lineHeight", value[0]);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { t } from "@lingui/macro";
|
||||
import { Check, DownloadSimple, Warning } from "@phosphor-icons/react";
|
||||
import { Check, DownloadSimple } from "@phosphor-icons/react";
|
||||
import {
|
||||
JsonResume,
|
||||
JsonResumeParser,
|
||||
@ -141,7 +141,6 @@ export const ImportDialog = () => {
|
||||
|
||||
toast({
|
||||
variant: "error",
|
||||
icon: <Warning size={16} weight="bold" />,
|
||||
title: t`An error occurred while validating the file.`,
|
||||
});
|
||||
}
|
||||
@ -186,7 +185,6 @@ export const ImportDialog = () => {
|
||||
} catch (error) {
|
||||
toast({
|
||||
variant: "error",
|
||||
icon: <Warning size={16} weight="bold" />,
|
||||
title: t`Oops, the server returned an error.`,
|
||||
description: importError?.message,
|
||||
});
|
||||
|
||||
@ -6,9 +6,9 @@ import { FAQSection } from "./sections/faq";
|
||||
import { FeaturesSection } from "./sections/features";
|
||||
import { HeroSection } from "./sections/hero";
|
||||
import { LogoCloudSection } from "./sections/logo-cloud";
|
||||
import { SampleResumesSection } from "./sections/sample-resumes";
|
||||
import { StatisticsSection } from "./sections/statistics";
|
||||
import { SupportSection } from "./sections/support";
|
||||
import { TemplatesSection } from "./sections/templates";
|
||||
import { TestimonialsSection } from "./sections/testimonials";
|
||||
|
||||
export const HomePage = () => (
|
||||
@ -23,7 +23,7 @@ export const HomePage = () => (
|
||||
<LogoCloudSection />
|
||||
<StatisticsSection />
|
||||
<FeaturesSection />
|
||||
<SampleResumesSection />
|
||||
<TemplatesSection />
|
||||
<TestimonialsSection />
|
||||
<SupportSection />
|
||||
<FAQSection />
|
||||
|
||||
@ -1,35 +1,22 @@
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { t } from "@lingui/macro";
|
||||
import { templatesList } from "@reactive-resume/utils";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const resumes = [
|
||||
"/sample-resumes/ditto",
|
||||
"/sample-resumes/ditto",
|
||||
"/sample-resumes/ditto",
|
||||
"/sample-resumes/ditto",
|
||||
];
|
||||
|
||||
export const SampleResumesSection = () => (
|
||||
export const TemplatesSection = () => (
|
||||
<section id="sample-resumes" className="relative py-24 sm:py-32">
|
||||
<div className="container flex flex-col gap-12 lg:min-h-[600px] lg:flex-row lg:items-start">
|
||||
<div className="space-y-4 lg:mt-16 lg:basis-96">
|
||||
<h2 className="text-4xl font-bold">{t`Sample Resumes`}</h2>
|
||||
<h2 className="text-4xl font-bold">{t`Templates`}</h2>
|
||||
|
||||
<Trans>
|
||||
<p className="leading-relaxed">
|
||||
Have a look at some of the resume created to showcase the templates available on
|
||||
Reactive Resume.
|
||||
</p>
|
||||
|
||||
<p className="leading-relaxed">
|
||||
They could also serve as examples to help guide the creation of your next resume.
|
||||
</p>
|
||||
</Trans>
|
||||
<p className="leading-relaxed">
|
||||
{t`Explore the templates available in Reactive Resume and view the resumes crafted with them. They could also serve as examples to help guide the creation of your next resume.`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full overflow-hidden lg:absolute lg:right-0 lg:max-w-[55%]">
|
||||
<motion.div
|
||||
animate={{
|
||||
x: [0, -400],
|
||||
x: [0, templatesList.length * 200 * -1],
|
||||
transition: {
|
||||
x: {
|
||||
duration: 30,
|
||||
@ -40,20 +27,21 @@ export const SampleResumesSection = () => (
|
||||
}}
|
||||
className="flex items-center gap-x-6"
|
||||
>
|
||||
{resumes.map((resume, index) => (
|
||||
{templatesList.map((template, index) => (
|
||||
<motion.a
|
||||
key={index}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={`${resume}.pdf`}
|
||||
href={`templates/pdf/${template}.pdf`}
|
||||
className="max-w-none flex-none"
|
||||
viewport={{ once: true }}
|
||||
initial={{ opacity: 0, x: -100 }}
|
||||
whileInView={{ opacity: 1, x: 0, transition: { delay: (index + 1) * 0.5 } }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
>
|
||||
<img
|
||||
alt={resume}
|
||||
src={`${resume}.jpg`}
|
||||
alt={template}
|
||||
loading="lazy"
|
||||
src={`/templates/jpg/${template}.jpg`}
|
||||
className=" aspect-[1/1.4142] h-[400px] rounded object-cover lg:h-[600px]"
|
||||
/>
|
||||
</motion.a>
|
||||
@ -1,7 +1,9 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { StatisticsDto, UrlDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { RESUME_KEY } from "@/client/constants/query-keys";
|
||||
import { toast } from "@/client/hooks/use-toast";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
@ -24,6 +26,15 @@ export const usePrintResume = () => {
|
||||
return { ...cache, downloads: cache.downloads + 1 } satisfies StatisticsDto;
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
const message = error?.message;
|
||||
|
||||
toast({
|
||||
variant: "error",
|
||||
title: t`Oops, the server returned an error.`,
|
||||
description: message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { printResume: printResumeFn, loading, error };
|
||||
|
||||
@ -40,7 +40,7 @@ export const configSchema = z.object({
|
||||
REDIS_URL: z.string().url().startsWith("redis://").optional(),
|
||||
|
||||
// Sentry
|
||||
SENTRY_DSN: z.string().url().startsWith("https://").optional(),
|
||||
VITE_SENTRY_DSN: z.string().url().startsWith("https://").optional(),
|
||||
|
||||
// Crowdin (Optional)
|
||||
CROWDIN_PROJECT_ID: z.coerce.number().optional(),
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { HealthIndicator, HealthIndicatorResult } from "@nestjs/terminus";
|
||||
import { withTimeout } from "@reactive-resume/utils";
|
||||
|
||||
import { PrinterService } from "../printer/printer.service";
|
||||
|
||||
@ -12,8 +11,7 @@ export class BrowserHealthIndicator extends HealthIndicator {
|
||||
|
||||
async isHealthy(): Promise<HealthIndicatorResult> {
|
||||
try {
|
||||
const version = await withTimeout(this.printerService.getVersion(), 5000);
|
||||
// const version = await this.printerService.getVersion();
|
||||
const version = await this.printerService.getVersion();
|
||||
|
||||
return this.getStatus("browser", true, { version });
|
||||
} catch (error) {
|
||||
|
||||
@ -23,7 +23,7 @@ async function bootstrap() {
|
||||
|
||||
// Sentry
|
||||
// Error Reporting and Performance Monitoring
|
||||
const sentryDsn = configService.get("SENTRY_DSN");
|
||||
const sentryDsn = configService.get("VITE_SENTRY_DSN");
|
||||
|
||||
if (sentryDsn) {
|
||||
const express = app.getHttpAdapter().getInstance();
|
||||
|
||||
@ -4,7 +4,7 @@ import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import fontkit from "@pdf-lib/fontkit";
|
||||
import { ResumeDto } from "@reactive-resume/dto";
|
||||
import { getFontUrls, withTimeout } from "@reactive-resume/utils";
|
||||
import { getFontUrls } from "@reactive-resume/utils";
|
||||
import { ErrorMessage } from "@reactive-resume/utils";
|
||||
import retry from "async-retry";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
@ -14,8 +14,6 @@ import { Config } from "../config/schema";
|
||||
import { StorageService } from "../storage/storage.service";
|
||||
import { UtilsService } from "../utils/utils.service";
|
||||
|
||||
const PRINTER_TIMEOUT = 10000; // 10 seconds
|
||||
|
||||
@Injectable()
|
||||
export class PrinterService {
|
||||
private readonly logger = new Logger(PrinterService.name);
|
||||
@ -55,11 +53,11 @@ export class PrinterService {
|
||||
async () => {
|
||||
const start = performance.now();
|
||||
|
||||
const url = await retry(() => withTimeout(this.generateResume(resume), PRINTER_TIMEOUT), {
|
||||
retries: 2,
|
||||
const url = await retry(() => this.generateResume(resume), {
|
||||
retries: 3,
|
||||
randomize: true,
|
||||
onRetry: (_, attempt) => {
|
||||
this.logger.debug(`Print Resume: retry attempt #${attempt}`);
|
||||
this.logger.log(`Retrying to print resume #${resume.id}, attempt #${attempt}`);
|
||||
},
|
||||
});
|
||||
|
||||
@ -76,117 +74,138 @@ export class PrinterService {
|
||||
async printPreview(resume: ResumeDto) {
|
||||
return this.utils.getCachedOrSet(
|
||||
`user:${resume.userId}:storage:previews:${resume.id}`,
|
||||
async () => withTimeout(this.generatePreview(resume), PRINTER_TIMEOUT),
|
||||
async () => {
|
||||
const start = performance.now();
|
||||
|
||||
const url = await retry(() => this.generatePreview(resume), {
|
||||
retries: 3,
|
||||
randomize: true,
|
||||
onRetry: (_, attempt) => {
|
||||
this.logger.log(
|
||||
`Retrying to generate preview of resume #${resume.id}, attempt #${attempt}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const duration = Number(performance.now() - start).toFixed(0);
|
||||
|
||||
this.logger.debug(`Chrome took ${duration}ms to generate preview`);
|
||||
|
||||
return url;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async generateResume(resume: ResumeDto) {
|
||||
const browser = await this.getBrowser();
|
||||
const page = await browser.newPage();
|
||||
try {
|
||||
const browser = await this.getBrowser();
|
||||
const page = await browser.newPage();
|
||||
|
||||
let url = this.utils.getUrl();
|
||||
const publicUrl = this.configService.getOrThrow<string>("PUBLIC_URL");
|
||||
const storageUrl = this.configService.getOrThrow<string>("STORAGE_URL");
|
||||
let url = this.utils.getUrl();
|
||||
const publicUrl = this.configService.getOrThrow<string>("PUBLIC_URL");
|
||||
const storageUrl = this.configService.getOrThrow<string>("STORAGE_URL");
|
||||
|
||||
if ([publicUrl, storageUrl].some((url) => url.includes("localhost"))) {
|
||||
// Switch client URL from `localhost` to `host.docker.internal` in development
|
||||
// This is required because the browser is running in a container and the client is running on the host machine.
|
||||
url = url.replace("localhost", "host.docker.internal");
|
||||
if ([publicUrl, storageUrl].some((url) => url.includes("localhost"))) {
|
||||
// Switch client URL from `localhost` to `host.docker.internal` in development
|
||||
// This is required because the browser is running in a container and the client is running on the host machine.
|
||||
url = url.replace("localhost", "host.docker.internal");
|
||||
|
||||
await page.setRequestInterception(true);
|
||||
await page.setRequestInterception(true);
|
||||
|
||||
// Intercept requests of `localhost` to `host.docker.internal` in development
|
||||
page.on("request", (request) => {
|
||||
if (request.url().startsWith(storageUrl)) {
|
||||
const modifiedUrl = request.url().replace("localhost", `host.docker.internal`);
|
||||
// Intercept requests of `localhost` to `host.docker.internal` in development
|
||||
page.on("request", (request) => {
|
||||
if (request.url().startsWith(storageUrl)) {
|
||||
const modifiedUrl = request.url().replace("localhost", `host.docker.internal`);
|
||||
|
||||
request.continue({ url: modifiedUrl });
|
||||
} else {
|
||||
request.continue();
|
||||
}
|
||||
});
|
||||
request.continue({ url: modifiedUrl });
|
||||
} else {
|
||||
request.continue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set the data of the resume to be printed in the browser's session storage
|
||||
const numPages = resume.data.metadata.layout.length;
|
||||
|
||||
await page.evaluateOnNewDocument((data) => {
|
||||
window.localStorage.setItem("resume", JSON.stringify(data));
|
||||
}, resume.data);
|
||||
|
||||
await page.goto(`${url}/artboard/preview`, { waitUntil: "networkidle0" });
|
||||
|
||||
const pagesBuffer: Buffer[] = [];
|
||||
|
||||
const processPage = async (index: number) => {
|
||||
const pageElement = await page.$(`[data-page="${index}"]`);
|
||||
const width = (await (await pageElement?.getProperty("scrollWidth"))?.jsonValue()) ?? 0;
|
||||
const height = (await (await pageElement?.getProperty("scrollHeight"))?.jsonValue()) ?? 0;
|
||||
|
||||
const tempHtml = await page.evaluate((element: HTMLDivElement) => {
|
||||
const clonedElement = element.cloneNode(true) as HTMLDivElement;
|
||||
const tempHtml = document.body.innerHTML;
|
||||
document.body.innerHTML = `${clonedElement.outerHTML}`;
|
||||
return tempHtml;
|
||||
}, pageElement);
|
||||
|
||||
pagesBuffer.push(await page.pdf({ width, height, printBackground: true }));
|
||||
|
||||
await page.evaluate((tempHtml: string) => {
|
||||
document.body.innerHTML = tempHtml;
|
||||
}, tempHtml);
|
||||
};
|
||||
|
||||
// Loop through all the pages and print them, by first displaying them, printing the PDF and then hiding them back
|
||||
for (let index = 1; index <= numPages; index++) {
|
||||
await processPage(index);
|
||||
}
|
||||
|
||||
// Using 'pdf-lib', merge all the pages from their buffers into a single PDF
|
||||
const pdf = await PDFDocument.create();
|
||||
pdf.registerFontkit(fontkit);
|
||||
|
||||
// Get information about fonts used in the resume from the metadata
|
||||
const fontData = resume.data.metadata.typography.font;
|
||||
const fontUrls = getFontUrls(fontData.family, fontData.variants);
|
||||
|
||||
// Load all the fonts from the URLs using HttpService
|
||||
const responses = await Promise.all(
|
||||
fontUrls.map((url) =>
|
||||
this.httpService.axiosRef.get(url, {
|
||||
responseType: "arraybuffer",
|
||||
}),
|
||||
),
|
||||
);
|
||||
const fontsBuffer = responses.map((response) => response.data as ArrayBuffer);
|
||||
|
||||
// Embed all the fonts in the PDF
|
||||
await Promise.all(fontsBuffer.map((buffer) => pdf.embedFont(buffer)));
|
||||
|
||||
for (let index = 0; index < pagesBuffer.length; index++) {
|
||||
const page = await PDFDocument.load(pagesBuffer[index]);
|
||||
const [copiedPage] = await pdf.copyPages(page, [0]);
|
||||
pdf.addPage(copiedPage);
|
||||
}
|
||||
|
||||
// Save the PDF to storage and return the URL to download the resume
|
||||
// Store the URL in cache for future requests, under the previously generated hash digest
|
||||
const buffer = Buffer.from(await pdf.save());
|
||||
|
||||
// This step will also save the resume URL in cache
|
||||
const resumeUrl = await this.storageService.uploadObject(
|
||||
resume.userId,
|
||||
"resumes",
|
||||
buffer,
|
||||
resume.id,
|
||||
);
|
||||
|
||||
// Close all the pages and disconnect from the browser
|
||||
await page.close();
|
||||
browser.disconnect();
|
||||
|
||||
return resumeUrl;
|
||||
} catch (error) {
|
||||
console.trace(error);
|
||||
}
|
||||
|
||||
// Set the data of the resume to be printed in the browser's session storage
|
||||
const numPages = resume.data.metadata.layout.length;
|
||||
|
||||
await page.evaluateOnNewDocument((data) => {
|
||||
window.localStorage.setItem("resume", JSON.stringify(data));
|
||||
}, resume.data);
|
||||
|
||||
await page.goto(`${url}/artboard/preview`, { waitUntil: "networkidle0" });
|
||||
|
||||
const pagesBuffer: Buffer[] = [];
|
||||
|
||||
const processPage = async (index: number) => {
|
||||
const pageElement = await page.$(`[data-page="${index}"]`);
|
||||
const width = (await (await pageElement?.getProperty("scrollWidth"))?.jsonValue()) ?? 0;
|
||||
const height = (await (await pageElement?.getProperty("scrollHeight"))?.jsonValue()) ?? 0;
|
||||
|
||||
const tempHtml = await page.evaluate((element: HTMLDivElement) => {
|
||||
const clonedElement = element.cloneNode(true) as HTMLDivElement;
|
||||
const tempHtml = document.body.innerHTML;
|
||||
document.body.innerHTML = `${clonedElement.outerHTML}`;
|
||||
return tempHtml;
|
||||
}, pageElement);
|
||||
|
||||
pagesBuffer.push(await page.pdf({ width, height, printBackground: true }));
|
||||
|
||||
await page.evaluate((tempHtml: string) => {
|
||||
document.body.innerHTML = tempHtml;
|
||||
}, tempHtml);
|
||||
};
|
||||
|
||||
// Loop through all the pages and print them, by first displaying them, printing the PDF and then hiding them back
|
||||
for (let index = 1; index <= numPages; index++) {
|
||||
await processPage(index);
|
||||
}
|
||||
|
||||
// Using 'pdf-lib', merge all the pages from their buffers into a single PDF
|
||||
const pdf = await PDFDocument.create();
|
||||
pdf.registerFontkit(fontkit);
|
||||
|
||||
// Get information about fonts used in the resume from the metadata
|
||||
const fontData = resume.data.metadata.typography.font;
|
||||
|
||||
const fontUrls = getFontUrls(fontData.family, fontData.variants);
|
||||
|
||||
// Load all the fonts from the URLs using HttpService
|
||||
const responses = await Promise.all(
|
||||
fontUrls.map((url) =>
|
||||
this.httpService.axiosRef.get(url, {
|
||||
responseType: "arraybuffer",
|
||||
}),
|
||||
),
|
||||
);
|
||||
const fontsBuffer = responses.map((response) => response.data as ArrayBuffer);
|
||||
|
||||
// Embed all the fonts in the PDF
|
||||
await Promise.all(fontsBuffer.map((buffer) => pdf.embedFont(buffer)));
|
||||
|
||||
for (let index = 0; index < pagesBuffer.length; index++) {
|
||||
const page = await PDFDocument.load(pagesBuffer[index]);
|
||||
const copiedPage = await pdf.copyPages(page, [0]);
|
||||
pdf.addPage(copiedPage[0]);
|
||||
}
|
||||
|
||||
// Save the PDF to storage and return the URL to download the resume
|
||||
// Store the URL in cache for future requests, under the previously generated hash digest
|
||||
const buffer = Buffer.from(await pdf.save());
|
||||
|
||||
// This step will also save the resume URL in cache
|
||||
const resumeUrl = await this.storageService.uploadObject(
|
||||
resume.userId,
|
||||
"resumes",
|
||||
buffer,
|
||||
resume.id,
|
||||
);
|
||||
|
||||
// Close all the pages and disconnect from the browser
|
||||
await page.close();
|
||||
browser.disconnect();
|
||||
|
||||
return resumeUrl;
|
||||
}
|
||||
|
||||
async generatePreview(resume: ResumeDto) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { CreateResumeDto, ImportResumeDto, ResumeDto, UpdateResumeDto } from "@reactive-resume/dto";
|
||||
import { defaultResumeData, ResumeData } from "@reactive-resume/schema";
|
||||
@ -18,7 +18,6 @@ import { UtilsService } from "../utils/utils.service";
|
||||
@Injectable()
|
||||
export class ResumeService {
|
||||
private readonly redis: Redis;
|
||||
private readonly logger = new Logger(ResumeService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
|
||||
@ -190,7 +190,7 @@ export class JsonResumeParser implements Parser<Json, JsonResume> {
|
||||
...defaultLanguage,
|
||||
id: createId(),
|
||||
name: language.language ?? "",
|
||||
fluency: language.fluency ?? "",
|
||||
description: language.fluency ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
...defaultLanguage,
|
||||
id: createId(),
|
||||
name: language.Name,
|
||||
fluency: language.Proficiency ?? "",
|
||||
description: language.Proficiency ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,8 +177,8 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
...defaultLanguage,
|
||||
id: createId(),
|
||||
name: language.name,
|
||||
fluency: language.level,
|
||||
fluencyLevel: Math.floor(language.levelNum / 2),
|
||||
description: language.level,
|
||||
level: Math.floor(language.levelNum / 2),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ export const sampleResume: ResumeData = {
|
||||
},
|
||||
customFields: [],
|
||||
picture: {
|
||||
url: "https://res.cloudinary.com/amruth-pillai/image/upload/v1699362669/reactive-resume/sample-resume/sample-picture_iitowc.jpg",
|
||||
size: 128,
|
||||
url: "https://i.imgur.com/HgwyOuJ.jpg",
|
||||
size: 70,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 6,
|
||||
effects: {
|
||||
@ -212,35 +212,35 @@ export const sampleResume: ResumeData = {
|
||||
id: "je6s7k590yp9wtv65bbcyiua",
|
||||
visible: true,
|
||||
name: "English",
|
||||
fluency: "Native Speaker",
|
||||
fluencyLevel: 5,
|
||||
description: "Native Speaker",
|
||||
level: 5,
|
||||
},
|
||||
{
|
||||
id: "qdltq93lqi5g290t70brrpit",
|
||||
visible: true,
|
||||
name: "German",
|
||||
fluency: "Advanced",
|
||||
fluencyLevel: 5,
|
||||
description: "Advanced",
|
||||
level: 5,
|
||||
},
|
||||
{
|
||||
id: "txxfl568h5yg5xojwj5sk3ms",
|
||||
visible: true,
|
||||
name: "French",
|
||||
fluency: "Advanced",
|
||||
fluencyLevel: 5,
|
||||
description: "Advanced",
|
||||
level: 5,
|
||||
},
|
||||
{
|
||||
id: "zla2ezdkhemy68kg8bj86jk0",
|
||||
visible: true,
|
||||
name: "Spanish",
|
||||
fluency: "Learning",
|
||||
fluencyLevel: 2,
|
||||
description: "Learning",
|
||||
level: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
profiles: {
|
||||
name: "Profiles",
|
||||
columns: 2,
|
||||
columns: 1,
|
||||
visible: true,
|
||||
id: "profiles",
|
||||
items: [
|
||||
@ -479,16 +479,16 @@ export const sampleResume: ResumeData = {
|
||||
theme: {
|
||||
background: "#ffffff",
|
||||
text: "#000000",
|
||||
primary: "#DC1A4E",
|
||||
primary: "#398dc9",
|
||||
},
|
||||
typography: {
|
||||
font: {
|
||||
family: "IBM Plex Serif",
|
||||
family: "IBM Plex Sans Condensed",
|
||||
subset: "latin",
|
||||
variants: ["regular", "italic", "500", "600", "600italic"],
|
||||
variants: ["regular", "500"],
|
||||
size: 13,
|
||||
},
|
||||
lineHeight: 2,
|
||||
lineHeight: 1.5,
|
||||
hideIcons: false,
|
||||
underlineLinks: true,
|
||||
},
|
||||
|
||||
@ -5,8 +5,8 @@ import { defaultItem, itemSchema } from "../shared";
|
||||
// Schema
|
||||
export const languageSchema = itemSchema.extend({
|
||||
name: z.string().min(1),
|
||||
fluency: z.string(),
|
||||
fluencyLevel: z.number().min(1).max(6),
|
||||
description: z.string(),
|
||||
level: z.number().min(0).max(5).default(1),
|
||||
});
|
||||
|
||||
// Type
|
||||
@ -16,6 +16,6 @@ export type Language = z.infer<typeof languageSchema>;
|
||||
export const defaultLanguage: Language = {
|
||||
...defaultItem,
|
||||
name: "",
|
||||
fluency: "",
|
||||
fluencyLevel: 1,
|
||||
description: "",
|
||||
level: 1,
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ import { defaultItem, itemSchema } from "../shared";
|
||||
export const skillSchema = itemSchema.extend({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
level: z.number().min(1).max(5).default(1),
|
||||
level: z.number().min(0).max(5).default(1),
|
||||
keywords: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
export * from "./namespaces/array";
|
||||
export * from "./namespaces/cefr";
|
||||
export * from "./namespaces/csv";
|
||||
export * from "./namespaces/date";
|
||||
export * from "./namespaces/error";
|
||||
export * from "./namespaces/fonts";
|
||||
export * from "./namespaces/languages";
|
||||
export * from "./namespaces/language";
|
||||
export * from "./namespaces/number";
|
||||
export * from "./namespaces/object";
|
||||
export * from "./namespaces/page";
|
||||
|
||||
@ -1,33 +1,5 @@
|
||||
import { LayoutLocator } from "./types";
|
||||
|
||||
type CombinationsInput<T> = {
|
||||
[K in keyof T]: T[K][];
|
||||
};
|
||||
|
||||
export const generateCombinations = <T>(obj: CombinationsInput<T>): Array<T> => {
|
||||
const keys = Object.keys(obj) as (keyof T)[];
|
||||
|
||||
const results: Array<T> = [];
|
||||
|
||||
function backtrack(combination: Partial<T>, index: number): void {
|
||||
if (index === keys.length) {
|
||||
results.push(combination as T);
|
||||
return;
|
||||
}
|
||||
|
||||
const key = keys[index];
|
||||
const values = obj[key];
|
||||
|
||||
for (const value of values) {
|
||||
backtrack({ ...combination, [key]: value }, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
backtrack({}, 0);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
// Function to find a specific item in a layout
|
||||
export const findItemInLayout = (item: string, layout: string[][][]): LayoutLocator | null => {
|
||||
for (let page = 0; page < layout.length; page++) {
|
||||
@ -60,17 +32,21 @@ export const moveItemInLayout = (
|
||||
target: LayoutLocator,
|
||||
layout: string[][][],
|
||||
): string[][][] => {
|
||||
// Create a deep copy of the layout to avoid mutating the original array
|
||||
const newLayout = JSON.parse(JSON.stringify(layout)) as string[][][];
|
||||
try {
|
||||
// Create a deep copy of the layout to avoid mutating the original array
|
||||
const newLayout = JSON.parse(JSON.stringify(layout)) as string[][][];
|
||||
|
||||
// Get the item from the current location
|
||||
const item = newLayout[current.page][current.column][current.section];
|
||||
// Get the item from the current location
|
||||
const item = newLayout[current.page][current.column][current.section];
|
||||
|
||||
// Remove the item from the current location
|
||||
newLayout[current.page][current.column].splice(current.section, 1);
|
||||
// Remove the item from the current location
|
||||
newLayout[current.page][current.column].splice(current.section, 1);
|
||||
|
||||
// Insert the item at the target location
|
||||
newLayout[target.page][target.column].splice(target.section, 0, item);
|
||||
// Insert the item at the target location
|
||||
newLayout[target.page][target.column].splice(target.section, 0, item);
|
||||
|
||||
return newLayout;
|
||||
return newLayout;
|
||||
} catch (error) {
|
||||
return layout;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
// CEFR Levels
|
||||
const cefrMap = {
|
||||
1: "A1",
|
||||
2: "A2",
|
||||
3: "B1",
|
||||
4: "B2",
|
||||
5: "C1",
|
||||
6: "C2",
|
||||
};
|
||||
|
||||
export const getCEFRLevel = (level: number) => {
|
||||
return cefrMap[level as keyof typeof cefrMap];
|
||||
};
|
||||
@ -4,6 +4,7 @@ export const sortByDate = <T>(a: T, b: T, key: keyof T, desc = true) => {
|
||||
if (!a[key] || !b[key]) return 0;
|
||||
if (!(a[key] instanceof Date) || !(b[key] instanceof Date)) return 0;
|
||||
|
||||
if (dayjs(a[key] as Date).isSame(dayjs(b[key] as Date))) return 0;
|
||||
if (desc) return dayjs(a[key] as Date).isBefore(dayjs(b[key] as Date)) ? 1 : -1;
|
||||
else return dayjs(a[key] as Date).isBefore(dayjs(b[key] as Date)) ? -1 : 1;
|
||||
};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// Languages
|
||||
export type Language = {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -4,4 +4,9 @@ export const linearTransform = (
|
||||
inMax: number,
|
||||
outMin: number,
|
||||
outMax: number,
|
||||
) => ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
||||
) => {
|
||||
if (inMax === inMin) return value === inMax ? outMin : NaN;
|
||||
return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
||||
};
|
||||
|
||||
// Handle this case: returns output minimum if input maximum equals input minimum
|
||||
|
||||
@ -1,11 +1 @@
|
||||
export const delay = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
|
||||
|
||||
export const withTimeout = async <T>(promise: Promise<T>, time: number): Promise<T> => {
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => {
|
||||
return reject(new Error(`Operation timed out after ${time}ms`));
|
||||
}, time),
|
||||
);
|
||||
|
||||
return Promise.race([promise, timeout]) as T;
|
||||
};
|
||||
|
||||
@ -1,46 +1,13 @@
|
||||
export type TemplateKey =
|
||||
| "onyx"
|
||||
| "kakuna"
|
||||
| "rhyhorn"
|
||||
| "azurill"
|
||||
| "ditto"
|
||||
| "chikorita"
|
||||
| "bronzor"
|
||||
| "pikachu";
|
||||
export const templatesList = [
|
||||
"azurill",
|
||||
"bronzor",
|
||||
"chikorita",
|
||||
"ditto",
|
||||
"kakuna",
|
||||
"nosepass",
|
||||
"onyx",
|
||||
"pikachu",
|
||||
"rhyhorn",
|
||||
] as const;
|
||||
|
||||
export type Template = { id: TemplateKey; name: string };
|
||||
|
||||
export const templatesList: Template[] = [
|
||||
{
|
||||
id: "onyx",
|
||||
name: "Onyx",
|
||||
},
|
||||
{
|
||||
id: "kakuna",
|
||||
name: "Kakuna",
|
||||
},
|
||||
{
|
||||
id: "rhyhorn",
|
||||
name: "Rhyhorn",
|
||||
},
|
||||
{
|
||||
id: "azurill",
|
||||
name: "Azurill",
|
||||
},
|
||||
{
|
||||
id: "ditto",
|
||||
name: "Ditto",
|
||||
},
|
||||
{
|
||||
id: "chikorita",
|
||||
name: "Chikorita",
|
||||
},
|
||||
{
|
||||
id: "bronzor",
|
||||
name: "Bronzor",
|
||||
},
|
||||
{
|
||||
id: "pikachu",
|
||||
name: "Pikachu",
|
||||
},
|
||||
];
|
||||
export type Template = (typeof templatesList)[number];
|
||||
|
||||
162
libs/utils/src/namespaces/tests/array.test.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { findItemInLayout, moveItemInLayout, removeItemInLayout } from "../array";
|
||||
|
||||
describe("findItemInLayout", () => {
|
||||
it("should find the correct location of an item", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const item = "item3";
|
||||
const expectedLocation = { page: 1, column: 0, section: 0 };
|
||||
|
||||
const location = findItemInLayout(item, layout);
|
||||
|
||||
expect(location).toEqual(expectedLocation);
|
||||
});
|
||||
|
||||
it("should return null if the item is not found", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const item = "item5";
|
||||
|
||||
const location = findItemInLayout(item, layout);
|
||||
|
||||
expect(location).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeItemInLayout", () => {
|
||||
it("should remove the item and return its location", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const item = "item3";
|
||||
const expectedLocation = { page: 1, column: 0, section: 0 };
|
||||
|
||||
const location = removeItemInLayout(item, layout);
|
||||
|
||||
expect(location).toEqual(expectedLocation);
|
||||
expect(layout[1][0]).not.toContain(item);
|
||||
});
|
||||
|
||||
it("should return null and not modify layout if the item is not found", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const item = "item5";
|
||||
|
||||
const location = removeItemInLayout(item, layout);
|
||||
|
||||
expect(location).toBeNull();
|
||||
expect(layout).toEqual([
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("moveItemInLayout", () => {
|
||||
it("should move an item from the current location to the target location", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const current = { page: 0, column: 1, section: 0 };
|
||||
const target = { page: 1, column: 0, section: 1 };
|
||||
const expectedLayout = [
|
||||
[["item1"], []],
|
||||
[["item3", "item2"], ["item4"]],
|
||||
];
|
||||
|
||||
const newLayout = moveItemInLayout(current, target, layout);
|
||||
|
||||
expect(newLayout).toEqual(expectedLayout);
|
||||
});
|
||||
|
||||
it("should not mutate the original layout array", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const layoutCopy = JSON.parse(JSON.stringify(layout));
|
||||
const current = { page: 0, column: 1, section: 0 };
|
||||
const target = { page: 1, column: 0, section: 1 };
|
||||
|
||||
moveItemInLayout(current, target, layout);
|
||||
|
||||
expect(layout).toEqual(layoutCopy);
|
||||
});
|
||||
|
||||
it("should handle the case where the current and target locations are the same", () => {
|
||||
const layout = [
|
||||
[["item1"], ["item2"]],
|
||||
[["item3"], ["item4"]],
|
||||
];
|
||||
const current = { page: 1, column: 0, section: 0 };
|
||||
const target = { page: 1, column: 0, section: 0 };
|
||||
|
||||
const newLayout = moveItemInLayout(current, target, layout);
|
||||
|
||||
expect(newLayout).toEqual(layout);
|
||||
});
|
||||
|
||||
it("moves an item to the specified target location", () => {
|
||||
const layout = [
|
||||
[["A", "B"], ["C"]],
|
||||
[["D"], ["E", "F"]],
|
||||
];
|
||||
const current = { page: 0, column: 0, section: 1 };
|
||||
const target = { page: 1, column: 1, section: 1 };
|
||||
const result = moveItemInLayout(current, target, layout);
|
||||
expect(result).toEqual([
|
||||
[["A"], ["C"]],
|
||||
[["D"], ["E", "B", "F"]],
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles moving an item within the same column", () => {
|
||||
const layout = [[["A", "B"]], [["C", "D"]]];
|
||||
const current = { page: 0, column: 0, section: 0 };
|
||||
const target = { page: 0, column: 0, section: 1 };
|
||||
const result = moveItemInLayout(current, target, layout);
|
||||
expect(result).toEqual([[["B", "A"]], [["C", "D"]]]);
|
||||
});
|
||||
|
||||
it("handles moving an item to the beginning of a column", () => {
|
||||
const layout = [[["A"], ["B", "C"]], [["D"]]];
|
||||
const current = { page: 1, column: 0, section: 0 };
|
||||
const target = { page: 0, column: 1, section: 0 };
|
||||
const result = moveItemInLayout(current, target, layout);
|
||||
expect(result).toEqual([[["A"], ["D", "B", "C"]], [[]]]);
|
||||
});
|
||||
|
||||
it("handles moving an item to an empty column", () => {
|
||||
const layout = [[["A"], []], [["B"]]];
|
||||
const current = { page: 0, column: 0, section: 0 };
|
||||
const target = { page: 0, column: 1, section: 0 };
|
||||
const result = moveItemInLayout(current, target, layout);
|
||||
expect(result).toEqual([[[], ["A"]], [["B"]]]);
|
||||
});
|
||||
|
||||
it("returns the same layout if the current location is invalid", () => {
|
||||
const layout = [[["A"], ["B"]]];
|
||||
const current = { page: 2, column: 0, section: 0 };
|
||||
const target = { page: 0, column: 1, section: 0 };
|
||||
const result = moveItemInLayout(current, target, layout);
|
||||
expect(result).toEqual(layout);
|
||||
});
|
||||
|
||||
it("returns the same layout if the target location is invalid", () => {
|
||||
const layout = [[["A"], ["B"]]];
|
||||
const current = { page: 0, column: 0, section: 0 };
|
||||
const target = { page: 2, column: 0, section: 0 };
|
||||
const result = moveItemInLayout(current, target, layout);
|
||||
expect(result).toEqual(layout);
|
||||
});
|
||||
});
|
||||
107
libs/utils/src/namespaces/tests/date.test.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { deepSearchAndParseDates, sortByDate } from "../date";
|
||||
|
||||
type TestType = { date?: Date };
|
||||
|
||||
describe("sortByDate", () => {
|
||||
it("sorts by date in descending order when desc is true", () => {
|
||||
const a: TestType = { date: new Date("2023-01-01") };
|
||||
const b: TestType = { date: new Date("2023-01-02") };
|
||||
expect(sortByDate(a, b, "date")).toBe(1);
|
||||
expect(sortByDate(b, a, "date")).toBe(-1);
|
||||
});
|
||||
|
||||
it("sorts by date in ascending order when desc is false", () => {
|
||||
const a: TestType = { date: new Date("2023-01-01") };
|
||||
const b: TestType = { date: new Date("2023-01-02") };
|
||||
expect(sortByDate(a, b, "date", false)).toBe(-1);
|
||||
expect(sortByDate(b, a, "date", false)).toBe(1);
|
||||
});
|
||||
|
||||
it("returns 0 if one of the dates is missing", () => {
|
||||
const a: TestType = { date: new Date("2023-01-01") };
|
||||
const b: TestType = {};
|
||||
expect(sortByDate(a, b, "date")).toBe(0);
|
||||
});
|
||||
|
||||
it("returns 0 if one of the values is not a date", () => {
|
||||
const a: TestType = { date: new Date("2023-01-01") };
|
||||
const b: TestType = { date: "2023-01-02" as unknown as Date };
|
||||
expect(sortByDate(a, b, "date")).toBe(0);
|
||||
});
|
||||
|
||||
it("handles equal dates", () => {
|
||||
const a: TestType = { date: new Date("2023-01-01") };
|
||||
const b: TestType = { date: new Date("2023-01-01") };
|
||||
expect(sortByDate(a, b, "date")).toBe(0);
|
||||
expect(sortByDate(a, b, "date", false)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deepSearchAndParseDates", () => {
|
||||
it("parses dates at various nesting levels", () => {
|
||||
const input = {
|
||||
level1: {
|
||||
date: "2021-08-17T00:00:00Z",
|
||||
nested: {
|
||||
date: "2022-08-17T00:00:00Z",
|
||||
},
|
||||
},
|
||||
otherKey: "value",
|
||||
};
|
||||
const dateKeys = ["date"];
|
||||
const output = deepSearchAndParseDates(input, dateKeys);
|
||||
|
||||
expect(output.level1.date).toBeInstanceOf(Date);
|
||||
expect(output.level1.nested.date).toBeInstanceOf(Date);
|
||||
expect(output.otherKey).toBe("value");
|
||||
});
|
||||
|
||||
it("does not parse invalid date strings", () => {
|
||||
const input = {
|
||||
date: "not a date",
|
||||
};
|
||||
const dateKeys = ["date"];
|
||||
const output = deepSearchAndParseDates(input, dateKeys);
|
||||
|
||||
expect(output.date).toBe("not a date");
|
||||
});
|
||||
|
||||
it("does not modify non-object input", () => {
|
||||
const input = "2021-08-17T00:00:00Z";
|
||||
const dateKeys = ["date"];
|
||||
const output = deepSearchAndParseDates(input, dateKeys);
|
||||
|
||||
expect(output).toBe(input);
|
||||
});
|
||||
|
||||
it("returns null for null input", () => {
|
||||
const input = null;
|
||||
const dateKeys = ["date"];
|
||||
const output = deepSearchAndParseDates(input, dateKeys);
|
||||
|
||||
expect(output).toBeNull();
|
||||
});
|
||||
|
||||
it("handles arrays with date strings", () => {
|
||||
const input = ["2021-08-17T00:00:00Z", "2022-08-17"];
|
||||
const dateKeys = ["0", "1"]; // Assuming the keys are stringified indices
|
||||
const output = deepSearchAndParseDates(input, dateKeys);
|
||||
|
||||
expect(output[0]).toBeInstanceOf(Date);
|
||||
expect(output[1]).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it("ignores keys that are not in the dateKeys", () => {
|
||||
const input = {
|
||||
date: "2021-08-17T00:00:00Z",
|
||||
notADate: "2021-08-17T00:00:00Z",
|
||||
};
|
||||
const dateKeys = ["date"];
|
||||
const output = deepSearchAndParseDates(input, dateKeys);
|
||||
|
||||
expect(output.date).toBeInstanceOf(Date);
|
||||
expect(output.notADate).toBe("2021-08-17T00:00:00Z");
|
||||
});
|
||||
});
|
||||
53
libs/utils/src/namespaces/tests/number.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { linearTransform } from "../number";
|
||||
|
||||
describe("linearTransform", () => {
|
||||
it("transforms values from one range to another", () => {
|
||||
const value = 5;
|
||||
const result = linearTransform(value, 0, 10, 0, 100);
|
||||
expect(result).toBe(50);
|
||||
});
|
||||
|
||||
it("handles negative ranges", () => {
|
||||
const value = -5;
|
||||
const result = linearTransform(value, -10, 0, 0, 100);
|
||||
expect(result).toBe(50);
|
||||
});
|
||||
|
||||
it("correctly transforms the minimum input value to the minimum output value", () => {
|
||||
const value = 0;
|
||||
const result = linearTransform(value, 0, 10, 0, 100);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it("correctly transforms the maximum input value to the maximum output value", () => {
|
||||
const value = 10;
|
||||
const result = linearTransform(value, 0, 10, 0, 100);
|
||||
expect(result).toBe(100);
|
||||
});
|
||||
|
||||
it("transforms values outside the input range", () => {
|
||||
const value = 15;
|
||||
const result = linearTransform(value, 0, 10, 0, 100);
|
||||
expect(result).toBe(150);
|
||||
});
|
||||
|
||||
it("handles inverted output ranges", () => {
|
||||
const value = 5;
|
||||
const result = linearTransform(value, 0, 10, 100, 0);
|
||||
expect(result).toBe(50);
|
||||
});
|
||||
|
||||
it("returns NaN if input maximum equals input minimum", () => {
|
||||
const value = 5;
|
||||
const result = linearTransform(value, 0, 0, 0, 100);
|
||||
expect(result).toBe(NaN);
|
||||
});
|
||||
|
||||
it("returns NaN if input range is zero (avoids division by zero)", () => {
|
||||
const value = 5;
|
||||
const result = linearTransform(value, 10, 10, 0, 100);
|
||||
expect(result).toBeNaN();
|
||||
});
|
||||
});
|
||||
75
libs/utils/src/namespaces/tests/object.test.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { exclude } from "../object";
|
||||
|
||||
describe("exclude", () => {
|
||||
type TestObject = {
|
||||
id: number;
|
||||
name: string;
|
||||
age: number;
|
||||
email: string;
|
||||
};
|
||||
|
||||
it("excludes specified keys from the object", () => {
|
||||
const object: TestObject = {
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
email: "alice@example.com",
|
||||
};
|
||||
const result = exclude(object, ["age", "email"]);
|
||||
|
||||
expect(result).toEqual({ id: 1, name: "Alice" });
|
||||
expect(result).not.toHaveProperty("age");
|
||||
expect(result).not.toHaveProperty("email");
|
||||
});
|
||||
|
||||
it("returns the same object if no keys are specified", () => {
|
||||
const object: TestObject = {
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
email: "alice@example.com",
|
||||
};
|
||||
const keysToExclude: Array<keyof TestObject> = [];
|
||||
const result = exclude(object, keysToExclude);
|
||||
|
||||
expect(result).toEqual(object);
|
||||
});
|
||||
|
||||
it("does not modify the original object", () => {
|
||||
const object: TestObject = {
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
email: "alice@example.com",
|
||||
};
|
||||
exclude(object, ["age", "email"]);
|
||||
|
||||
expect(object).toHaveProperty("age");
|
||||
expect(object).toHaveProperty("email");
|
||||
});
|
||||
|
||||
it("handles cases where keys to exclude are not present in the object", () => {
|
||||
const object: TestObject = {
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
email: "alice@example.com",
|
||||
};
|
||||
const keysToExclude = ["nonExistentKey" as keyof TestObject];
|
||||
const result = exclude(object, keysToExclude);
|
||||
|
||||
expect(result).toEqual(object);
|
||||
});
|
||||
|
||||
it("returns the input if it is not an object", () => {
|
||||
const object: unknown = null;
|
||||
const keysToExclude = ["id"];
|
||||
|
||||
// @ts-expect-error passing invalid input type for tests
|
||||
const result = exclude(object, keysToExclude);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
66
libs/utils/src/namespaces/tests/string.test.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
extractUrl,
|
||||
generateRandomName,
|
||||
getInitials,
|
||||
isEmptyString,
|
||||
isUrl,
|
||||
kebabCase,
|
||||
processUsername,
|
||||
} from "../string";
|
||||
|
||||
describe("getInitials", () => {
|
||||
it("returns the initials of a name", () => {
|
||||
expect(getInitials("John Doe")).toBe("JD");
|
||||
expect(getInitials("Mary Jane Watson")).toBe("MW");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isUrl", () => {
|
||||
it("checks if a string is a URL", () => {
|
||||
expect(isUrl("https://example.com")).toBe(true);
|
||||
expect(isUrl("not a url")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isEmptyString", () => {
|
||||
it("checks if a string is empty or only contains whitespace", () => {
|
||||
expect(isEmptyString("")).toBe(true);
|
||||
expect(isEmptyString(" ")).toBe(true);
|
||||
expect(isEmptyString("<p></p>")).toBe(true);
|
||||
expect(isEmptyString("not empty")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractUrl", () => {
|
||||
it("extracts a URL from a string", () => {
|
||||
expect(extractUrl("Visit https://example.com today!")).toBe("https://example.com");
|
||||
expect(extractUrl("No URL here.")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("kebabCase", () => {
|
||||
it("converts a string to kebab-case", () => {
|
||||
expect(kebabCase("fooBar")).toBe("foo-bar");
|
||||
expect(kebabCase("Foo Bar")).toBe("foo-bar");
|
||||
expect(kebabCase("foo_bar")).toBe("foo-bar");
|
||||
expect(kebabCase("")).toBe("");
|
||||
expect(kebabCase(null)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateRandomName", () => {
|
||||
it("generates a random name", () => {
|
||||
const name = generateRandomName();
|
||||
expect(name).toMatch(/^[A-Z][a-z]+ [A-Z][a-z]+ [A-Z][a-z]+$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processUsername", () => {
|
||||
it("processes a username by removing non-alphanumeric characters", () => {
|
||||
expect(processUsername("User@Name!")).toBe("username");
|
||||
expect(processUsername("")).toBe("");
|
||||
expect(processUsername(null)).toBe("");
|
||||
});
|
||||
});
|
||||
34925
package-lock.json
generated
39
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@reactive-resume/source",
|
||||
"description": "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.",
|
||||
"version": "4.0.0-alpha.3",
|
||||
"version": "4.0.0-alpha.4",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"author": {
|
||||
@ -16,13 +16,16 @@
|
||||
"scripts": {
|
||||
"dev": "nx run-many -t serve",
|
||||
"test": "nx run-many -t test",
|
||||
"prebuild": "npx prisma generate",
|
||||
"prebuild": "pnpm prisma:generate",
|
||||
"build": "nx run-many -t build",
|
||||
"prestart": "npx prisma migrate deploy",
|
||||
"prestart": "pnpm prisma:migrate",
|
||||
"start": "node dist/apps/server/main",
|
||||
"lint": "nx run-many -t lint --fix",
|
||||
"format": "npx prettier -w .",
|
||||
"messages:extract": "npx lingui extract --clean --overwrite --locale en-US"
|
||||
"format": "pnpm exec prettier -w .",
|
||||
"prisma:generate": "pnpm exec prisma generate",
|
||||
"prisma:migrate": "pnpm exec prisma migrate deploy",
|
||||
"prisma:migrate:dev": "pnpm exec prisma migrate dev",
|
||||
"messages:extract": "pnpm exec lingui extract --clean --overwrite --locale en-US"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.3",
|
||||
@ -46,12 +49,12 @@
|
||||
"@nx/webpack": "17.1.2",
|
||||
"@nx/workspace": "17.1.2",
|
||||
"@swc-node/register": "~1.6.8",
|
||||
"@swc/cli": "~0.1.62",
|
||||
"@swc/cli": "~0.1.63",
|
||||
"@swc/core": "~1.3.96",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@tanstack/eslint-plugin-query": "^5.8.3",
|
||||
"@tanstack/eslint-plugin-query": "^5.8.4",
|
||||
"@testing-library/react": "14.1.0",
|
||||
"@types/async-retry": "^1.4.8",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
@ -63,7 +66,7 @@
|
||||
"@types/lodash.get": "^4.4.9",
|
||||
"@types/lodash.set": "^4.3.9",
|
||||
"@types/multer": "^1.4.10",
|
||||
"@types/node": "20.9.0",
|
||||
"@types/node": "20.9.1",
|
||||
"@types/nodemailer": "^6.4.14",
|
||||
"@types/papaparse": "^5.3.11",
|
||||
"@types/passport": "^1.0.15",
|
||||
@ -77,12 +80,12 @@
|
||||
"@types/webfontloader": "^1.6.37",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@vitejs/plugin-react": "~4.1.1",
|
||||
"@vitejs/plugin-react-swc": "~3.4.1",
|
||||
"@vitejs/plugin-react": "~4.2.0",
|
||||
"@vitejs/plugin-react-swc": "~3.5.0",
|
||||
"@vitest/coverage-v8": "^0.34.6",
|
||||
"@vitest/ui": "~0.34.6",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"cypress": "^13.5.0",
|
||||
"cypress": "^13.5.1",
|
||||
"eslint": "~8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
@ -103,19 +106,19 @@
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prisma": "^5.6.0",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "~5.2.2",
|
||||
"vite": "~4.5.0",
|
||||
"vite": "~5.0.0",
|
||||
"vite-plugin-dts": "~3.6.3",
|
||||
"vitest": "~0.34.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
"prisma": "^5.6.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource/ibm-plex-sans": "^5.0.17",
|
||||
@ -133,7 +136,7 @@
|
||||
"@nestjs/passport": "^10.0.2",
|
||||
"@nestjs/platform-express": "^10.2.8",
|
||||
"@nestjs/serve-static": "^4.0.0",
|
||||
"@nestjs/swagger": "^7.1.15",
|
||||
"@nestjs/swagger": "^7.1.16",
|
||||
"@nestjs/terminus": "^10.1.1",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
@ -166,7 +169,7 @@
|
||||
"@songkeys/nestjs-redis": "^10.0.0",
|
||||
"@songkeys/nestjs-redis-health": "^10.0.0",
|
||||
"@swc/helpers": "~0.5.3",
|
||||
"@tanstack/react-query": "^5.8.3",
|
||||
"@tanstack/react-query": "^5.8.4",
|
||||
"@tiptap/extension-highlight": "^2.1.12",
|
||||
"@tiptap/extension-image": "^2.1.12",
|
||||
"@tiptap/extension-link": "^2.1.12",
|
||||
@ -176,7 +179,7 @@
|
||||
"@tiptap/starter-kit": "^2.1.12",
|
||||
"@types/passport-jwt": "^3.0.13",
|
||||
"async-retry": "^1.3.3",
|
||||
"axios": "^1.6.1",
|
||||
"axios": "^1.6.2",
|
||||
"axios-auth-refresh": "^3.3.6",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
@ -200,7 +203,7 @@
|
||||
"nestjs-prisma": "^0.22.0",
|
||||
"nestjs-zod": "^3.0.0",
|
||||
"nodemailer": "^6.9.7",
|
||||
"openai": "^4.17.5",
|
||||
"openai": "^4.19.0",
|
||||
"otplib": "^12.0.1",
|
||||
"papaparse": "^5.4.1",
|
||||
"passport": "^0.6.0",
|
||||
@ -218,7 +221,7 @@
|
||||
"react-hook-form": "^7.48.2",
|
||||
"react-parallax-tilt": "^1.7.174",
|
||||
"react-resizable-panels": "^0.0.54",
|
||||
"react-router-dom": "6.18.0",
|
||||
"react-router-dom": "6.19.0",
|
||||
"react-zoom-pan-pinch": "^3.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
19750
pnpm-lock.yaml
generated
Normal file
@ -46,9 +46,11 @@ services:
|
||||
ports:
|
||||
- ${CHROME_PORT:-8080}:3000
|
||||
environment:
|
||||
TOKEN: ${CHROME_TOKEN:-chrome_token}
|
||||
KEEP_ALIVE: true
|
||||
CONNECTION_TIMEOUT: 10000
|
||||
EXIT_ON_HEALTH_FAILURE: true
|
||||
PRE_REQUEST_HEALTH_CHECK: true
|
||||
TOKEN: ${CHROME_TOKEN:-chrome_token}
|
||||
|
||||
# Redis (for cache & server session management)
|
||||
redis:
|
||||
|
||||
134
tools/compose/nginx-proxy-manager.yml
Normal file
@ -0,0 +1,134 @@
|
||||
version: "3"
|
||||
|
||||
# In this Docker Compose example, we use Nginx Proxy Manager to manage the reverse proxy and SSL certificates.
|
||||
# There's very little configuration to be made on the compose file itself, and most of it is done on the Nginx Proxy Manager UI.
|
||||
|
||||
services:
|
||||
# Database (Postgres)
|
||||
postgres:
|
||||
image: postgres:alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Storage (for image uploads)
|
||||
minio:
|
||||
image: minio/minio
|
||||
restart: unless-stopped
|
||||
command: server /data
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.storage.rule=Host(`storage.example.com`)
|
||||
- traefik.http.routers.storage.entrypoints=websecure
|
||||
- traefik.http.routers.storage.tls.certresolver=letsencrypt
|
||||
- traefik.http.services.storage.loadbalancer.server.port=9000
|
||||
|
||||
# Chrome Browser (for printing and previews)
|
||||
chrome:
|
||||
image: browserless/chrome:1.61.0-puppeteer-21.4.1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TOKEN: chrome_token
|
||||
EXIT_ON_HEALTH_FAILURE: true
|
||||
PRE_REQUEST_HEALTH_CHECK: true
|
||||
|
||||
# Redis (for cache & server session management)
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --save 60 1 --loglevel warning --requirepass password
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
app:
|
||||
image: amruthpillai/reactive-resume:v4.0.0-alpha.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- postgres
|
||||
- minio
|
||||
- redis
|
||||
- chrome
|
||||
environment:
|
||||
# -- Environment Variables --
|
||||
PORT: 3000
|
||||
NODE_ENV: production
|
||||
|
||||
# -- URLs --
|
||||
PUBLIC_URL: https://example.com
|
||||
STORAGE_URL: https://storage.example.com
|
||||
|
||||
# -- Printer (Chrome) --
|
||||
CHROME_TOKEN: chrome_token
|
||||
CHROME_URL: wss://printer.example.com
|
||||
|
||||
# -- Database (Postgres) --
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
||||
|
||||
# -- Auth --
|
||||
ACCESS_TOKEN_SECRET: access_token_secret
|
||||
REFRESH_TOKEN_SECRET: refresh_token_secret
|
||||
|
||||
# -- Emails --
|
||||
# SMTP_URL: smtp://user:pass@smtp:587 # Optional
|
||||
|
||||
# -- Storage (Minio) --
|
||||
STORAGE_ENDPOINT: minio
|
||||
STORAGE_PORT: 9000
|
||||
STORAGE_REGION: us-east-1 # Optional
|
||||
STORAGE_BUCKET: default
|
||||
STORAGE_ACCESS_KEY: minioadmin
|
||||
STORAGE_SECRET_KEY: minioadmin
|
||||
|
||||
# -- Cache (Redis) --
|
||||
REDIS_URL: redis://default:password@redis:6379
|
||||
|
||||
# -- Sentry --
|
||||
# VITE_SENTRY_DSN: https://id.sentry.io # Optional
|
||||
|
||||
# -- Crowdin (Optional) --
|
||||
# CROWDIN_PROJECT_ID:
|
||||
# CROWDIN_ACCESS_TOKEN:
|
||||
|
||||
# -- GitHub --
|
||||
GITHUB_CLIENT_ID: github_client_id
|
||||
GITHUB_CLIENT_SECRET: github_client_secret
|
||||
GITHUB_CALLBACK_URL: https://example.com/api/auth/github/callback
|
||||
|
||||
# -- Google --
|
||||
GOOGLE_CLIENT_ID: google_client_id
|
||||
GOOGLE_CLIENT_SECRET: google_client_secret
|
||||
GOOGLE_CALLBACK_URL: https://example.com/api/auth/google/callback
|
||||
|
||||
nginx:
|
||||
image: jc21/nginx-proxy-manager
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 81:81 # Port 81 is used for Proxy Manager's Web UI
|
||||
volumes:
|
||||
- nginx_data:/data
|
||||
- letsencrypt_data:/etc/letsencrypt
|
||||
environment:
|
||||
DISABLE_IPV6: true
|
||||
|
||||
volumes:
|
||||
minio_data:
|
||||
redis_data:
|
||||
nginx_data:
|
||||
postgres_data:
|
||||
letsencrypt_data:
|
||||
@ -94,7 +94,7 @@ services:
|
||||
REDIS_URL: redis://default:password@redis:6379
|
||||
|
||||
# -- Sentry --
|
||||
# SENTRY_DSN: https://id.sentry.io # Optional
|
||||
# VITE_SENTRY_DSN: https://id.sentry.io # Optional
|
||||
|
||||
# -- Crowdin (Optional) --
|
||||
# CROWDIN_PROJECT_ID:
|
||||
|
||||
@ -105,7 +105,7 @@ services:
|
||||
REDIS_URL: redis://default:password@redis:6379
|
||||
|
||||
# -- Sentry --
|
||||
# SENTRY_DSN: https://id.sentry.io # Optional
|
||||
# VITE_SENTRY_DSN: https://id.sentry.io # Optional
|
||||
|
||||
# -- Crowdin (Optional) --
|
||||
# CROWDIN_PROJECT_ID:
|
||||
|
||||
@ -99,7 +99,7 @@ services:
|
||||
REDIS_URL: redis://default:password@redis:6379
|
||||
|
||||
# -- Sentry --
|
||||
# SENTRY_DSN: https://id.sentry.io # Optional
|
||||
# VITE_SENTRY_DSN: https://id.sentry.io # Optional
|
||||
|
||||
# -- Crowdin (Optional) --
|
||||
# CROWDIN_PROJECT_ID:
|
||||
|
||||