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;