diff --git a/src/components/admin/Resume/index.tsx b/src/components/admin/Resume/index.tsx
new file mode 100644
index 00000000..bc8d3749
--- /dev/null
+++ b/src/components/admin/Resume/index.tsx
@@ -0,0 +1,46 @@
+import { GifSafeImage, Typography } from '@/components/common';
+import { config } from '@/lib';
+import { PublicResume } from '@/lib/types/apiResponses';
+import { getProfilePicture } from '@/lib/utils';
+import Link from 'next/link';
+import { BsDownload } from 'react-icons/bs';
+import styles from './style.module.scss';
+
+interface ResumeProps {
+ resume: PublicResume;
+}
+
+const Resume = ({ resume }: ResumeProps) => {
+ const fileName = decodeURIComponent(resume.url.split('/').at(-1) ?? 'resume.pdf');
+
+ return (
+
+ {resume.user ? (
+
+
+
+ {resume.user.firstName} {resume.user.lastName}
+
+
+ {resume.user.graduationYear} {resume.user.major}
+
+
+ ) : null}
+
+
+
{fileName}
+
+ Uploaded {new Date(resume.lastUpdated).toLocaleDateString('en-US', { dateStyle: 'full' })}
+
+
+
+ );
+};
+
+export default Resume;
diff --git a/src/components/admin/Resume/style.module.scss b/src/components/admin/Resume/style.module.scss
new file mode 100644
index 00000000..b1fafaf8
--- /dev/null
+++ b/src/components/admin/Resume/style.module.scss
@@ -0,0 +1,73 @@
+@use 'src/styles/vars.scss' as vars;
+
+.wrapper {
+ background-color: var(--theme-elevated-background);
+ border: 1px solid var(--theme-elevated-stroke);
+ border-radius: 0.5rem;
+ display: flex;
+ gap: 1rem;
+ padding: 1rem;
+ word-break: break-word;
+
+ @media (max-width: vars.$breakpoint-md) {
+ flex-direction: column;
+ gap: 2rem;
+ }
+
+ .user,
+ .resume {
+ align-items: center;
+ display: grid;
+ gap: 0.5rem 1rem;
+ grid-template-areas:
+ 'image name'
+ 'image info';
+ grid-template-columns: auto 1fr;
+
+ .image {
+ grid-area: image;
+ }
+
+ .name {
+ grid-area: name;
+ }
+
+ &:hover .name {
+ text-decoration: underline;
+ }
+
+ .info {
+ grid-area: info;
+ }
+ }
+
+ .user {
+ flex: 1 0 0;
+
+ .image {
+ border-radius: 5rem;
+ object-fit: contain;
+ }
+ }
+
+ .resume {
+ flex: 1.5 0 0;
+
+ .image {
+ height: 1.5rem;
+ width: 1.5rem;
+
+ @media (max-width: vars.$breakpoint-md) {
+ width: 3rem;
+ }
+ }
+
+ .name {
+ color: var(--theme-blue-text-button);
+ }
+
+ .info {
+ color: var(--theme-text-on-background-2);
+ }
+ }
+}
diff --git a/src/components/admin/Resume/style.module.scss.d.ts b/src/components/admin/Resume/style.module.scss.d.ts
new file mode 100644
index 00000000..fe443828
--- /dev/null
+++ b/src/components/admin/Resume/style.module.scss.d.ts
@@ -0,0 +1,14 @@
+export type Styles = {
+ image: string;
+ info: string;
+ name: string;
+ resume: string;
+ user: string;
+ wrapper: string;
+};
+
+export type ClassNames = keyof Styles;
+
+declare const styles: Styles;
+
+export default styles;
diff --git a/src/lib/api/ResumeAPI.ts b/src/lib/api/ResumeAPI.ts
index adddd0e0..92b71706 100644
--- a/src/lib/api/ResumeAPI.ts
+++ b/src/lib/api/ResumeAPI.ts
@@ -1,10 +1,11 @@
import { config } from '@/lib';
import type { UUID } from '@/lib/types';
import { PatchResumeRequest } from '@/lib/types/apiRequests';
-import type {
- PatchResumeResponse,
- PublicResume,
- UpdateResumeResponse,
+import {
+ GetVisibleResumesResponse,
+ type PatchResumeResponse,
+ type PublicResume,
+ type UpdateResumeResponse,
} from '@/lib/types/apiResponses';
import axios from 'axios';
@@ -76,3 +77,20 @@ export const deleteResume = async (token: string, uuid: UUID): Promise =>
},
});
};
+
+/**
+ * Get all visible resumes
+ * @param token Authorization bearer token. User needs to be admin or
+ * sponsorship manager
+ */
+export const getResumes = async (token: string): Promise => {
+ const requestUrl = `${config.api.baseUrl}${config.api.endpoints.resume}`;
+
+ const response = await axios.get(requestUrl, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ return response.data.resumes;
+};
diff --git a/src/pages/admin/resumes.tsx b/src/pages/admin/resumes.tsx
new file mode 100644
index 00000000..1a3e2242
--- /dev/null
+++ b/src/pages/admin/resumes.tsx
@@ -0,0 +1,40 @@
+import Resume from '@/components/admin/Resume';
+import { Typography } from '@/components/common';
+import { config } from '@/lib';
+import { ResumeAPI } from '@/lib/api';
+import withAccessType, { GetServerSidePropsWithAuth } from '@/lib/hoc/withAccessType';
+import { PermissionService } from '@/lib/services';
+import type { PublicResume } from '@/lib/types/apiResponses';
+import styles from '@/styles/pages/resumes.module.scss';
+
+interface AdminResumePageProps {
+ resumes: PublicResume[];
+}
+
+const AdminResumePage = ({ resumes }: AdminResumePageProps) => {
+ return (
+
+
+ Resumes
+
+ {resumes.map(resume => (
+
+ ))}
+
+ );
+};
+
+export default AdminResumePage;
+
+const getServerSidePropsFunc: GetServerSidePropsWithAuth = async ({
+ authToken,
+}) => {
+ const resumes = await ResumeAPI.getResumes(authToken);
+ return { props: { title: 'View Resumes', resumes } };
+};
+
+export const getServerSideProps = withAccessType(
+ getServerSidePropsFunc,
+ PermissionService.canViewResumes,
+ { redirectTo: config.homeRoute }
+);
diff --git a/src/styles/pages/resumes.module.scss b/src/styles/pages/resumes.module.scss
new file mode 100644
index 00000000..d985fe9b
--- /dev/null
+++ b/src/styles/pages/resumes.module.scss
@@ -0,0 +1,7 @@
+.page {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin: 0 auto;
+ max-width: 60rem;
+}
diff --git a/src/styles/pages/resumes.module.scss.d.ts b/src/styles/pages/resumes.module.scss.d.ts
new file mode 100644
index 00000000..190f135c
--- /dev/null
+++ b/src/styles/pages/resumes.module.scss.d.ts
@@ -0,0 +1,9 @@
+export type Styles = {
+ page: string;
+};
+
+export type ClassNames = keyof Styles;
+
+declare const styles: Styles;
+
+export default styles;