Reactive-Resume/client/pages/[username]/[slug]/index.tsx

129 lines
3.7 KiB
TypeScript

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(`/api${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;