mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-12 15:52:56 +10:00
run prettier formatting on all files
This commit is contained in:
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +1,2 @@
|
|||||||
github: AmruthPillai
|
github: AmruthPillai
|
||||||
custom: https://paypal.me/RajaRajanA
|
custom: https://paypal.me/amruthde
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@ -1,9 +1,9 @@
|
|||||||
name: 🐞 Bug Report
|
name: 🐞 Bug Report
|
||||||
description: Create a bug report to help improve Reactive Resume.
|
description: Create a bug report to help improve Reactive Resume.
|
||||||
|
|
||||||
title: "[Bug] <title>"
|
title: '[Bug] <title>'
|
||||||
labels: [Bug, Needs Triage]
|
labels: [Bug, Needs Triage]
|
||||||
assignees: "AmruthPillai"
|
assignees: 'AmruthPillai'
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
4
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
@ -1,9 +1,9 @@
|
|||||||
name: ✨ Feature Request
|
name: ✨ Feature Request
|
||||||
description: Suggest an feature or idea that you would like to see in Reactive Resume.
|
description: Suggest an feature or idea that you would like to see in Reactive Resume.
|
||||||
|
|
||||||
title: "[Feature] <title>"
|
title: '[Feature] <title>'
|
||||||
labels: [Feature, Needs Triage]
|
labels: [Feature, Needs Triage]
|
||||||
assignees: "AmruthPillai"
|
assignees: 'AmruthPillai'
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
|||||||
50
client/components/home/sections/Hero.tsx
Normal file
50
client/components/home/sections/Hero.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import Tilt from 'react-parallax-tilt';
|
||||||
|
|
||||||
|
import { defaultTiltProps } from '@/constants/tilt';
|
||||||
|
|
||||||
|
import HomeActions from '../Actions';
|
||||||
|
import HeroBackground from '../Background';
|
||||||
|
import HeroPattern from '../Pattern';
|
||||||
|
|
||||||
|
const HeroSection = () => (
|
||||||
|
<section className="relative">
|
||||||
|
<HeroPattern />
|
||||||
|
<HeroBackground />
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-7xl px-6 pb-24 pt-10 sm:pb-32 lg:flex lg:px-8 lg:py-52">
|
||||||
|
<div className="mx-auto max-w-2xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-12">
|
||||||
|
<div className="mt-10 space-y-2">
|
||||||
|
<h6 className="text-base font-bold tracking-wide">Finally,</h6>
|
||||||
|
<h1 className="text-4xl font-bold !leading-[1.15] tracking-tight sm:text-6xl">
|
||||||
|
A free and open-source resume builder
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="prose prose-base prose-zinc mt-6 text-lg leading-8 dark:prose-invert">
|
||||||
|
Reactive Resume is a free and open-source resume builder that simplifies the tasks of creating, updating, and
|
||||||
|
sharing your resume.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-12 space-x-4">
|
||||||
|
<HomeActions />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-32">
|
||||||
|
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
|
||||||
|
<Tilt {...defaultTiltProps}>
|
||||||
|
<img
|
||||||
|
width={2432}
|
||||||
|
height={1442}
|
||||||
|
src="/images/screenshots/builder.png"
|
||||||
|
alt="Reactive Resume Screenshot - Builder Screen"
|
||||||
|
className="w-[76rem] rounded-lg bg-zinc-50/5 shadow-2xl ring-1 ring-zinc-950/10 dark:bg-zinc-950/5 dark:ring-zinc-50/10"
|
||||||
|
/>
|
||||||
|
</Tilt>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default HeroSection;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
.content {
|
.content {
|
||||||
@apply rounded px-6 text-sm shadow lg:w-1/2 xl:w-2/5;
|
@apply rounded px-6 text-sm shadow lg:w-2/3 xl:w-1/2;
|
||||||
@apply absolute inset-4 sm:inset-x-4 sm:inset-y-auto lg:inset-auto;
|
@apply absolute inset-4 sm:inset-x-4 sm:inset-y-auto lg:inset-auto;
|
||||||
@apply overflow-scroll bg-zinc-100 dark:bg-zinc-900 lg:overflow-auto;
|
@apply overflow-scroll bg-zinc-100 dark:bg-zinc-900 lg:overflow-auto;
|
||||||
@apply max-h-[90vh] min-h-fit;
|
@apply max-h-[90vh] min-h-fit;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export const Copyright = ({ className }: Props) => (
|
|||||||
<div
|
<div
|
||||||
className={clsx('prose prose-sm prose-zinc flex flex-col gap-y-1 text-xs opacity-40 dark:prose-invert', className)}
|
className={clsx('prose prose-sm prose-zinc flex flex-col gap-y-1 text-xs opacity-40 dark:prose-invert', className)}
|
||||||
>
|
>
|
||||||
<span className="font-medium">v4.0.0</span>
|
<span className="font-medium">v{process.env.appVersion}</span>
|
||||||
<span>
|
<span>
|
||||||
Licensed under <a href="https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE">MIT</a>
|
Licensed under <a href="https://github.com/AmruthPillai/Reactive-Resume/blob/main/LICENSE">MIT</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const ThemeSwitch = styled(Switch)(({ theme }) => ({
|
|||||||
transform: 'translateX(22px)',
|
transform: 'translateX(22px)',
|
||||||
'& .MuiSwitch-thumb:before': {
|
'& .MuiSwitch-thumb:before': {
|
||||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||||
'#fff'
|
'#fff',
|
||||||
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
|
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
|
||||||
},
|
},
|
||||||
'& + .MuiSwitch-track': {
|
'& + .MuiSwitch-track': {
|
||||||
@ -36,7 +36,7 @@ const ThemeSwitch = styled(Switch)(({ theme }) => ({
|
|||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||||
'#fff'
|
'#fff',
|
||||||
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
|
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -54,5 +54,5 @@ export const languageMap: Record<string, Language> = languages.reduce(
|
|||||||
...acc,
|
...acc,
|
||||||
[lang.code]: lang,
|
[lang.code]: lang,
|
||||||
}),
|
}),
|
||||||
{}
|
{},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss';
|
|||||||
|
|
||||||
// Links
|
// Links
|
||||||
export const DOCS_URL = 'https://docs.rxresu.me';
|
export const DOCS_URL = 'https://docs.rxresu.me';
|
||||||
export const DONATION_URL = 'https://paypal.me/RajaRajanA';
|
export const DONATION_URL = 'https://paypal.me/amruthde';
|
||||||
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
|
export const TRANSLATE_URL = 'https://translate.rxresu.me/';
|
||||||
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
|
export const DIGITALOCEAN_URL = 'https://pillai.xyz/digitalocean';
|
||||||
export const REDDIT_URL = 'https://www.reddit.com/r/reactiveresume/';
|
export const REDDIT_URL = 'https://www.reddit.com/r/reactiveresume/';
|
||||||
|
|||||||
@ -121,7 +121,7 @@ const LoginModal: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<p>{t('modals.auth.login.body')}</p>
|
<p>{t('modals.auth.login.body')}</p>
|
||||||
|
|
||||||
<form className="grid gap-4 xl:w-2/3">
|
<form className="grid gap-4">
|
||||||
<Controller
|
<Controller
|
||||||
name="identifier"
|
name="identifier"
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@ -62,7 +62,7 @@
|
|||||||
"webfontloader": "^1.6.28"
|
"webfontloader": "^1.6.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.8",
|
"@babel/core": "^7.22.9",
|
||||||
"@fontsource/ibm-plex-sans": "^5.0.5",
|
"@fontsource/ibm-plex-sans": "^5.0.5",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
@ -70,7 +70,7 @@
|
|||||||
"@types/lodash": "^4.14.195",
|
"@types/lodash": "^4.14.195",
|
||||||
"@types/node": "^20.4.1",
|
"@types/node": "^20.4.1",
|
||||||
"@types/react": "^18.2.14",
|
"@types/react": "^18.2.14",
|
||||||
"@types/react-dom": "^18.2.6",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-redux": "^7.1.25",
|
"@types/react-redux": "^7.1.25",
|
||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.2",
|
||||||
"@types/webfontloader": "^1.6.35",
|
"@types/webfontloader": "^1.6.35",
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import type { GetStaticProps, NextPage } from 'next';
|
import type { GetStaticProps, NextPage } from 'next';
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
import Tilt from 'react-parallax-tilt';
|
|
||||||
|
|
||||||
import HeroBackground from '@/components/home/Background';
|
|
||||||
import Footer from '@/components/home/Footer';
|
import Footer from '@/components/home/Footer';
|
||||||
import Header from '@/components/home/Header';
|
import Header from '@/components/home/Header';
|
||||||
import HeroPattern from '@/components/home/Pattern';
|
import HeroSection from '@/components/home/sections/Hero';
|
||||||
import LogoSection from '@/components/home/sections/Logo';
|
import LogoSection from '@/components/home/sections/Logo';
|
||||||
import StatsSection from '@/components/home/sections/Stats';
|
import StatsSection from '@/components/home/sections/Stats';
|
||||||
import { defaultTiltProps } from '@/constants/tilt';
|
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => ({
|
||||||
props: {
|
props: {
|
||||||
@ -21,43 +18,13 @@ const Home: NextPage = () => (
|
|||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main className="relative isolate mb-[450px] overflow-hidden bg-zinc-50 dark:bg-zinc-950">
|
<main className="relative isolate mb-[450px] overflow-hidden bg-zinc-50 dark:bg-zinc-950">
|
||||||
<section className="relative">
|
{/* Hero */}
|
||||||
<HeroPattern />
|
<HeroSection />
|
||||||
<HeroBackground />
|
|
||||||
|
|
||||||
<div className="mx-auto max-w-7xl px-6 pb-24 pt-10 sm:pb-32 lg:flex lg:px-8 lg:py-52">
|
|
||||||
<div className="mx-auto max-w-2xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-12">
|
|
||||||
<div className="mt-10 space-y-2">
|
|
||||||
<h6 className="text-base font-bold tracking-wide">Finally,</h6>
|
|
||||||
<h1 className="text-4xl font-bold !leading-[1.15] tracking-tight sm:text-6xl">
|
|
||||||
A free and open-source resume builder
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="prose prose-base prose-zinc mt-6 text-lg leading-8 dark:prose-invert">
|
|
||||||
Reactive Resume is a free and open-source resume builder that simplifies the tasks of creating, updating,
|
|
||||||
and sharing your resume.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-32">
|
|
||||||
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
|
|
||||||
<Tilt {...defaultTiltProps}>
|
|
||||||
<img
|
|
||||||
width={2432}
|
|
||||||
height={1442}
|
|
||||||
src="/images/screenshots/builder.png"
|
|
||||||
alt="Reactive Resume Screenshot - Builder Screen"
|
|
||||||
className="w-[76rem] rounded-lg bg-zinc-50/5 shadow-2xl ring-1 ring-zinc-950/10 dark:bg-zinc-950/5 dark:ring-zinc-50/10"
|
|
||||||
/>
|
|
||||||
</Tilt>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
{/* Logo Cloud */}
|
||||||
<LogoSection />
|
<LogoSection />
|
||||||
|
|
||||||
|
{/* Statistics */}
|
||||||
<StatsSection />
|
<StatsSection />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export const resetPassword = async (resetPasswordParams: ResetPasswordParams) =>
|
|||||||
export const updateProfile = async (updateProfileParams: UpdateProfileParams) => {
|
export const updateProfile = async (updateProfileParams: UpdateProfileParams) => {
|
||||||
const { data: user } = await axios.patch<User, AxiosResponse<User>, UpdateProfileParams>(
|
const { data: user } = await axios.patch<User, AxiosResponse<User>, UpdateProfileParams>(
|
||||||
'/auth/update-profile',
|
'/auth/update-profile',
|
||||||
updateProfileParams
|
updateProfileParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
store.dispatch(setUser(user));
|
store.dispatch(setUser(user));
|
||||||
|
|||||||
@ -46,7 +46,7 @@ axios.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default axios;
|
export default axios;
|
||||||
|
|||||||
@ -9,6 +9,6 @@ export type PrintResumeAsPdfParams = {
|
|||||||
export const printResumeAsPdf = (printResumeAsPdfParams: PrintResumeAsPdfParams): Promise<string> =>
|
export const printResumeAsPdf = (printResumeAsPdfParams: PrintResumeAsPdfParams): Promise<string> =>
|
||||||
axios
|
axios
|
||||||
.get(
|
.get(
|
||||||
`/printer/${printResumeAsPdfParams.username}/${printResumeAsPdfParams.slug}?lastUpdated=${printResumeAsPdfParams.lastUpdated}`
|
`/printer/${printResumeAsPdfParams.username}/${printResumeAsPdfParams.slug}?lastUpdated=${printResumeAsPdfParams.lastUpdated}`,
|
||||||
)
|
)
|
||||||
.then((res) => res.data);
|
.then((res) => res.data);
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const DEBOUNCE_WAIT = 1000;
|
|||||||
|
|
||||||
const debouncedSync = debounce(
|
const debouncedSync = debounce(
|
||||||
(resume: Resume, dispatch: AppDispatch) => updateResume(resume).then((resume) => dispatch(setResume(resume))),
|
(resume: Resume, dispatch: AppDispatch) => updateResume(resume).then((resume) => dispatch(setResume(resume))),
|
||||||
DEBOUNCE_WAIT
|
DEBOUNCE_WAIT,
|
||||||
);
|
);
|
||||||
|
|
||||||
function* handleSync(dispatch: AppDispatch) {
|
function* handleSync(dispatch: AppDispatch) {
|
||||||
@ -31,7 +31,7 @@ function* handleSync(dispatch: AppDispatch) {
|
|||||||
|
|
||||||
function* syncSaga(dispatch: AppDispatch) {
|
function* syncSaga(dispatch: AppDispatch) {
|
||||||
yield takeLatest([setResumeState, addItem, editItem, duplicateItem, deleteItem, addSection, deleteSection], () =>
|
yield takeLatest([setResumeState, addItem, editItem, duplicateItem, deleteItem, addSection, deleteSection], () =>
|
||||||
handleSync(dispatch)
|
handleSync(dispatch),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
|||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
|||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const MastheadSidebar: React.FC = () => {
|
|||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
const primaryColor: string = useAppSelector((state) => get(state.resume.present, 'metadata.theme.primary'));
|
||||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
|||||||
const Masthead = () => {
|
const Masthead = () => {
|
||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
|||||||
const Masthead: React.FC = () => {
|
const Masthead: React.FC = () => {
|
||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {} as ThemeConfig));
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
|||||||
const Masthead: React.FC = () => {
|
const Masthead: React.FC = () => {
|
||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
const { name, photo, email, phone, website, birthdate, headline, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ThemeConfig } from 'schema';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
import { ThemeConfig } from 'schema';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { ThemeConfig } from 'schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { ThemeConfig } from 'schema';
|
||||||
|
|
||||||
import Markdown from '@/components/shared/Markdown';
|
import Markdown from '@/components/shared/Markdown';
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
@ -16,7 +16,7 @@ import { addHttp, formatLocation, getPhotoClassNames } from '@/utils/template';
|
|||||||
export const MastheadSidebar: React.FC = () => {
|
export const MastheadSidebar: React.FC = () => {
|
||||||
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
const dateFormat: string = useAppSelector((state) => get(state.resume.present, 'metadata.date.format'));
|
||||||
const { name, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { ListItem, Location, PhotoFilters } from 'schema';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isArray from 'lodash/isArray';
|
import isArray from 'lodash/isArray';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { ListItem, Location, PhotoFilters } from 'schema';
|
||||||
|
|
||||||
export type Required<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
export type Required<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export const parseListItemPath = (item: ListItem, path: string | string[], separ
|
|||||||
|
|
||||||
export const getPhotoClassNames = (filters: PhotoFilters) =>
|
export const getPhotoClassNames = (filters: PhotoFilters) =>
|
||||||
clsx({
|
clsx({
|
||||||
'object-cover': true,
|
'object-cover aspect-square': true,
|
||||||
grayscale: filters.grayscale,
|
grayscale: filters.grayscale,
|
||||||
'!border-[4px] !border-solid': filters.border,
|
'!border-[4px] !border-solid': filters.border,
|
||||||
'rounded-lg': filters.shape === 'rounded-square',
|
'rounded-lg': filters.shape === 'rounded-square',
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const FontWrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) =
|
|||||||
const WebFont = (await import('webfontloader')).default;
|
const WebFont = (await import('webfontloader')).default;
|
||||||
const families = Object.values(typography.family).reduce(
|
const families = Object.values(typography.family).reduce(
|
||||||
(acc, family) => [...acc, `${family}:400,600,700`],
|
(acc, family) => [...acc, `${family}:400,600,700`],
|
||||||
[] as string[]
|
[] as string[],
|
||||||
);
|
);
|
||||||
|
|
||||||
WebFont.load({ google: { families } });
|
WebFont.load({ google: { families } });
|
||||||
|
|||||||
11
package.json
11
package.json
@ -3,9 +3,9 @@
|
|||||||
"version": "3.8.1",
|
"version": "3.8.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run dev",
|
"dev": "dotenv -- turbo run dev",
|
||||||
"build": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run build",
|
"build": "dotenv -- turbo run build",
|
||||||
"start": "env-cmd --silent cross-var cross-env VERSION=$npm_package_version turbo run start",
|
"start": "dotenv -- turbo run start",
|
||||||
"format": "prettier --write --loglevel silent --cache .",
|
"format": "prettier --write --loglevel silent --cache .",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"update-dependencies": "ncu -x nanoid --deep -u && pnpm install",
|
"update-dependencies": "ncu -x nanoid --deep -u && pnpm install",
|
||||||
@ -17,10 +17,7 @@
|
|||||||
"server"
|
"server"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-env": "^7.0.3",
|
"dotenv-cli": "^7.2.1"
|
||||||
"cross-var": "^1.1.0",
|
|
||||||
"env-cmd": "^10.1.0",
|
|
||||||
"uuid": "^9.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.4.1",
|
"@types/node": "^20.4.1",
|
||||||
|
|||||||
1006
pnpm-lock.yaml
generated
1006
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export class AuthService {
|
|||||||
private schedulerRegistry: SchedulerRegistry,
|
private schedulerRegistry: SchedulerRegistry,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
private jwtService: JwtService
|
private jwtService: JwtService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async register(registerDto: RegisterDto) {
|
async register(registerDto: RegisterDto) {
|
||||||
@ -49,7 +49,7 @@ export class AuthService {
|
|||||||
if (!isPasswordMatching) {
|
if (!isPasswordMatching) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'The username/email and password combination provided was incorrect.',
|
'The username/email and password combination provided was incorrect.',
|
||||||
HttpStatus.UNAUTHORIZED
|
HttpStatus.UNAUTHORIZED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ export class AuthService {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'The username/email and password combination provided was incorrect.',
|
'The username/email and password combination provided was incorrect.',
|
||||||
HttpStatus.UNAUTHORIZED
|
HttpStatus.UNAUTHORIZED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,10 @@ import { UsersService } from '@/users/users.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
constructor(configService: ConfigService, private readonly usersService: UsersService) {
|
constructor(
|
||||||
|
configService: ConfigService,
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
secretOrKey: configService.get('auth.jwtSecret'),
|
secretOrKey: configService.get('auth.jwtSecret'),
|
||||||
|
|||||||
@ -9,7 +9,10 @@ import cachedResponse from './assets/cachedResponse.json';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FontsService {
|
export class FontsService {
|
||||||
constructor(private configService: ConfigService, private httpService: HttpService) {}
|
constructor(
|
||||||
|
private configService: ConfigService,
|
||||||
|
private httpService: HttpService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getAll(): Promise<Font[]> {
|
async getAll(): Promise<Font[]> {
|
||||||
const apiKey = this.configService.get('google.apiKey');
|
const apiKey = this.configService.get('google.apiKey');
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export class HealthController {
|
|||||||
constructor(
|
constructor(
|
||||||
private health: HealthCheckService,
|
private health: HealthCheckService,
|
||||||
private http: HttpHealthIndicator,
|
private http: HttpHealthIndicator,
|
||||||
private db: TypeOrmHealthIndicator
|
private db: TypeOrmHealthIndicator,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export class PrinterController {
|
|||||||
async printAsPdf(
|
async printAsPdf(
|
||||||
@Param('username') username: string,
|
@Param('username') username: string,
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@Query('lastUpdated') lastUpdated: string
|
@Query('lastUpdated') lastUpdated: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return await this.printerService.printAsPdf(username, slug, lastUpdated);
|
return await this.printerService.printAsPdf(username, slug, lastUpdated);
|
||||||
|
|||||||
@ -49,7 +49,10 @@ const minimal_chromium_args = [
|
|||||||
export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
||||||
private browser: BrowserContext;
|
private browser: BrowserContext;
|
||||||
|
|
||||||
constructor(private readonly schedulerRegistry: SchedulerRegistry, private readonly configService: ConfigService) {}
|
constructor(
|
||||||
|
private readonly schedulerRegistry: SchedulerRegistry,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
this.browser = await chromium.launchPersistentContext('.playwright', {
|
this.browser = await chromium.launchPersistentContext('.playwright', {
|
||||||
@ -84,7 +87,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
|||||||
this.schedulerRegistry.deleteTimeout(`delete-${file}`);
|
this.schedulerRegistry.deleteTimeout(`delete-${file}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -99,7 +102,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
|||||||
|
|
||||||
const pageFormat: PageConfig['format'] = await page.$$eval(
|
const pageFormat: PageConfig['format'] = await page.$$eval(
|
||||||
'[data-page]',
|
'[data-page]',
|
||||||
(pages) => pages[0].getAttribute('data-format') as PageConfig['format']
|
(pages) => pages[0].getAttribute('data-format') as PageConfig['format'],
|
||||||
);
|
);
|
||||||
|
|
||||||
const resumePages = await page.$$eval('[data-page]', (pages) =>
|
const resumePages = await page.$$eval('[data-page]', (pages) =>
|
||||||
@ -107,7 +110,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
|||||||
pageNumber: index + 1,
|
pageNumber: index + 1,
|
||||||
innerHTML: page.innerHTML,
|
innerHTML: page.innerHTML,
|
||||||
height: page.clientHeight,
|
height: page.clientHeight,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const pdf = await PDFDocument.create();
|
const pdf = await PDFDocument.create();
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export class ResumeController {
|
|||||||
findOneByShortId(
|
findOneByShortId(
|
||||||
@Param('shortId') shortId: string,
|
@Param('shortId') shortId: string,
|
||||||
@User('id') userId?: number,
|
@User('id') userId?: number,
|
||||||
@Query('secretKey') secretKey?: string
|
@Query('secretKey') secretKey?: string,
|
||||||
) {
|
) {
|
||||||
return this.resumeService.findOneByShortId(shortId, userId, secretKey);
|
return this.resumeService.findOneByShortId(shortId, userId, secretKey);
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ export class ResumeController {
|
|||||||
@Param('username') username: string,
|
@Param('username') username: string,
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@User('id') userId?: number,
|
@User('id') userId?: number,
|
||||||
@Query('secretKey') secretKey?: string
|
@Query('secretKey') secretKey?: string,
|
||||||
) {
|
) {
|
||||||
return this.resumeService.findOneByIdentifier(username, slug, userId, secretKey);
|
return this.resumeService.findOneByIdentifier(username, slug, userId, secretKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export class UsersService {
|
|||||||
@InjectRepository(User) private userRepository: Repository<User>,
|
@InjectRepository(User) private userRepository: Repository<User>,
|
||||||
private schedulerRegistry: SchedulerRegistry,
|
private schedulerRegistry: SchedulerRegistry,
|
||||||
private mailService: MailService,
|
private mailService: MailService,
|
||||||
private dataSource: DataSource
|
private dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findById(id: number): Promise<User> {
|
async findById(id: number): Promise<User> {
|
||||||
@ -115,7 +115,7 @@ export class UsersService {
|
|||||||
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'Please wait at least 30 minutes before resetting your password again.',
|
'Please wait at least 30 minutes before resetting your password again.',
|
||||||
HttpStatus.TOO_MANY_REQUESTS
|
HttpStatus.TOO_MANY_REQUESTS,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
|
|||||||
Reference in New Issue
Block a user