diff --git a/apps/portal/package.json b/apps/portal/package.json
index f135cab3..d57cdf43 100644
--- a/apps/portal/package.json
+++ b/apps/portal/package.json
@@ -27,6 +27,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.36.1",
+ "react-pdf": "^5.7.2",
"react-query": "^3.39.2",
"superjson": "^1.10.0",
"zod": "^3.18.0"
@@ -37,6 +38,7 @@
"@types/node": "^18.0.0",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
+ "@types/react-pdf": "^5.7.2",
"autoprefixer": "^10.4.12",
"postcss": "^8.4.16",
"prettier-plugin-tailwindcss": "^0.1.13",
diff --git a/apps/portal/public/test_resume.pdf b/apps/portal/public/test_resume.pdf
new file mode 100644
index 00000000..279b6a25
Binary files /dev/null and b/apps/portal/public/test_resume.pdf differ
diff --git a/apps/portal/src/components/resumes/ResumePdf.tsx b/apps/portal/src/components/resumes/ResumePdf.tsx
new file mode 100644
index 00000000..12debea4
--- /dev/null
+++ b/apps/portal/src/components/resumes/ResumePdf.tsx
@@ -0,0 +1,48 @@
+import { useState } from 'react';
+import { Document, Page, pdfjs } from 'react-pdf';
+import type { PDFDocumentProxy } from 'react-pdf/node_modules/pdfjs-dist';
+import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
+import { Button, Spinner } from '@tih/ui';
+
+pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
+
+export default function ResumePdf() {
+ const [numPages, setNumPages] = useState(0);
+ const [pageNumber] = useState(1);
+
+ const onPdfLoadSuccess = (pdf: PDFDocumentProxy) => {
+ setNumPages(pdf.numPages);
+ };
+
+ return (
+
+
}
+ onLoadSuccess={onPdfLoadSuccess}>
+
+
+
+
+
+
+ Page {pageNumber} of {numPages}
+
+
+
+
+ );
+}
diff --git a/apps/portal/src/components/resumes/comments/CommentsForm.tsx b/apps/portal/src/components/resumes/comments/CommentsForm.tsx
new file mode 100644
index 00000000..a20bfd0c
--- /dev/null
+++ b/apps/portal/src/components/resumes/comments/CommentsForm.tsx
@@ -0,0 +1,149 @@
+import { useState } from 'react';
+import type { SubmitHandler } from 'react-hook-form';
+import { useForm } from 'react-hook-form';
+import { Button, Dialog, TextInput } from '@tih/ui';
+
+type CommentsFormProps = Readonly<{
+ setShowCommentsForm: (show: boolean) => void;
+}>;
+
+type IFormInput = {
+ education: string;
+ experience: string;
+ general: string;
+ projects: string;
+ skills: string;
+};
+
+type InputKeys = keyof IFormInput;
+
+export default function CommentsForm({
+ setShowCommentsForm,
+}: CommentsFormProps) {
+ const [showDialog, setShowDialog] = useState(false);
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ formState: { isDirty },
+ } = useForm({
+ defaultValues: {
+ education: '',
+ experience: '',
+ general: '',
+ projects: '',
+ skills: '',
+ },
+ });
+
+ // TODO: Implement mutation to database
+ const onSubmit: SubmitHandler = (data) => {
+ alert(JSON.stringify(data));
+ };
+
+ const onCancel = () => {
+ if (isDirty) {
+ setShowDialog(true);
+ } else {
+ setShowCommentsForm(false);
+ }
+ };
+
+ const onValueChange = (section: InputKeys, value: string) => {
+ setValue(section, value.trim(), { shouldDirty: true });
+ };
+
+ return (
+ <>
+ Add your review
+
+
+
+
+ >
+ );
+}
diff --git a/apps/portal/src/components/resumes/comments/CommentsList.tsx b/apps/portal/src/components/resumes/comments/CommentsList.tsx
new file mode 100644
index 00000000..397c9551
--- /dev/null
+++ b/apps/portal/src/components/resumes/comments/CommentsList.tsx
@@ -0,0 +1,32 @@
+import { useState } from 'react';
+import { Button, Tabs } from '@tih/ui';
+
+import { COMMENTS_SECTIONS } from './constants';
+
+type CommentsListProps = Readonly<{
+ setShowCommentsForm: (show: boolean) => void;
+}>;
+
+export default function CommentsList({
+ setShowCommentsForm,
+}: CommentsListProps) {
+ const [tab, setTab] = useState(COMMENTS_SECTIONS[0].value);
+
+ return (
+ <>
+