mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-14 00:32:35 +10:00
🚀 release v3.0.0
This commit is contained in:
75
client/pages/[username]/[slug]/build.tsx
Normal file
75
client/pages/[username]/[slug]/build.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import Head from 'next/head';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import Center from '@/components/build/Center/Center';
|
||||
import LeftSidebar from '@/components/build/LeftSidebar/LeftSidebar';
|
||||
import RightSidebar from '@/components/build/RightSidebar/RightSidebar';
|
||||
import { fetchResumeByIdentifier } from '@/services/resume';
|
||||
import { useAppDispatch } from '@/store/hooks';
|
||||
import { setResume } from '@/store/resume/resumeSlice';
|
||||
import styles from '@/styles/pages/Build.module.scss';
|
||||
|
||||
type QueryParams = {
|
||||
username: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
username: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async ({ query, locale = 'en' }) => {
|
||||
const { username, slug } = query as QueryParams;
|
||||
|
||||
return {
|
||||
props: { username, slug, ...(await serverSideTranslations(locale, ['common', 'modals', 'builder'])) },
|
||||
};
|
||||
};
|
||||
|
||||
const Build: NextPage<Props> = ({ username, slug }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data: resume } = useQuery<Resume>(
|
||||
`resume/${username}/${slug}`,
|
||||
() => fetchResumeByIdentifier({ username, slug }),
|
||||
{
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
onSuccess: (resume) => {
|
||||
dispatch(setResume(resume));
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (resume) dispatch(setResume(resume));
|
||||
}, [resume, dispatch]);
|
||||
|
||||
if (!resume || isEmpty(resume)) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Head>
|
||||
<title>
|
||||
{resume.name} | {t('common.title')}
|
||||
</title>
|
||||
</Head>
|
||||
|
||||
<LeftSidebar />
|
||||
<Center />
|
||||
<RightSidebar />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Build;
|
||||
128
client/pages/[username]/[slug]/index.tsx
Normal file
128
client/pages/[username]/[slug]/index.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import { Download, Downloading } from '@mui/icons-material';
|
||||
import { ButtonBase } from '@mui/material';
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import download from 'downloadjs';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import Page from '@/components/build/Center/Page';
|
||||
import { ServerError } from '@/services/axios';
|
||||
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
|
||||
import { fetchResumeByIdentifier } from '@/services/resume';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setResume } from '@/store/resume/resumeSlice';
|
||||
import styles from '@/styles/pages/Preview.module.scss';
|
||||
|
||||
type QueryParams = {
|
||||
slug: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
slug: string;
|
||||
resume?: Resume;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async ({ query, locale = 'en' }) => {
|
||||
const { username, slug } = query as QueryParams;
|
||||
|
||||
try {
|
||||
const resume = await fetchResumeByIdentifier({ username, slug });
|
||||
|
||||
return {
|
||||
props: { username, slug, resume, ...(await serverSideTranslations(locale, ['common'])) },
|
||||
};
|
||||
} catch {
|
||||
return { props: { username, slug, ...(await serverSideTranslations(locale, ['common'])) } };
|
||||
}
|
||||
};
|
||||
|
||||
const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData && !isEmpty(initialData)) {
|
||||
dispatch(setResume(initialData));
|
||||
}
|
||||
}, [dispatch, initialData]);
|
||||
|
||||
useQuery<Resume>(`resume/${username}/${slug}`, () => fetchResumeByIdentifier({ username, slug }), {
|
||||
initialData,
|
||||
retry: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
onSuccess: (data) => {
|
||||
dispatch(setResume(data));
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorObj = JSON.parse(JSON.stringify(error));
|
||||
const statusCode: number = get(errorObj, 'status', 404);
|
||||
|
||||
if (statusCode === 404) {
|
||||
toast.error('The resume you were looking for does not exist, or maybe it never did?');
|
||||
|
||||
router.push('/');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
|
||||
|
||||
if (isEmpty(resume)) return null;
|
||||
|
||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const url = await mutateAsync({ username, slug });
|
||||
|
||||
download(url);
|
||||
} catch {
|
||||
toast.error('Something went wrong, please try again later.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('preview-mode', styles.container)}>
|
||||
{layout.map((_, pageIndex) => (
|
||||
<Page key={pageIndex} page={pageIndex} />
|
||||
))}
|
||||
|
||||
<div className={clsx(styles.download, { 'opacity-75': isLoading })}>
|
||||
<ButtonBase onClick={handleDownload} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Downloading />
|
||||
<h4>Please wait</h4>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download />
|
||||
<h4>Download PDF</h4>
|
||||
</>
|
||||
)}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
|
||||
<p className={styles.footer}>
|
||||
Made with <Link href="/">Reactive Resume</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preview;
|
||||
66
client/pages/[username]/[slug]/printer.tsx
Normal file
66
client/pages/[username]/[slug]/printer.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { Resume } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import Page from '@/components/build/Center/Page';
|
||||
import { fetchResumeByIdentifier } from '@/services/resume';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setResume } from '@/store/resume/resumeSlice';
|
||||
import styles from '@/styles/pages/Printer.module.scss';
|
||||
|
||||
type QueryParams = {
|
||||
slug: string;
|
||||
username: string;
|
||||
secretKey?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
resume?: Resume;
|
||||
redirect?: any;
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, QueryParams> = async ({ query }) => {
|
||||
const { username, slug, secretKey } = query as QueryParams;
|
||||
|
||||
try {
|
||||
if (isEmpty(secretKey)) throw new Error('There is no secret key!');
|
||||
|
||||
const resume = await fetchResumeByIdentifier({ username, slug, options: { secretKey } });
|
||||
|
||||
return { props: { resume } };
|
||||
} catch (error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: '/',
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Printer: NextPage<Props> = ({ resume: initialData }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) dispatch(setResume(initialData));
|
||||
}, [dispatch, initialData]);
|
||||
|
||||
if (!resume || isEmpty(resume)) return null;
|
||||
|
||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||
|
||||
return (
|
||||
<div className={clsx('printer-mode', styles.container)}>
|
||||
{layout.map((_, pageIndex) => (
|
||||
<Page key={pageIndex} page={pageIndex} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Printer;
|
||||
Reference in New Issue
Block a user