Skip to content

Commit a1eb94b

Browse files
committed
Merge branch 'main' into RecruiterApplicants
1 parent 3d920f8 commit a1eb94b

File tree

5 files changed

+271
-16
lines changed

5 files changed

+271
-16
lines changed

backend/routers/user_profile_router.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,58 @@ def get_user_profile_picture_me(
117117
filename={current_user.username}_profile_picture.jpg"
118118
}
119119
)
120+
121+
@router.get(
122+
"/users/profile/{user_id}/profile_picture",
123+
status_code=status.HTTP_200_OK,
124+
tags=["UserProfile"],
125+
summary="Retrieve the profile picture of the user with id == user_id from the database.",
126+
description="Retrieve the specified users' profile picture from the database as a jpg file.",
127+
response_description="The specified users' profile picture."
128+
)
129+
def get_user_profile_picture(db: Session = Depends(dependencies.get_db),
130+
user_id: int = Path()):
131+
132+
"""
133+
GET route to obtain a user's profile picture by user_id.
134+
135+
Parameters
136+
----------
137+
db: Session
138+
a database session
139+
user_id: int
140+
the user's unique identifier
141+
142+
Returns
143+
-------
144+
fastapi.Response
145+
a response with the profile picture
146+
"""
147+
148+
# Check that the user has a profile
149+
dependencies.user_profile_exists(db, user_id)
150+
151+
user_profile = user_profile_crud.get_user_profile_by_id(db, user_id)
152+
153+
# Check that the user has a profile picture
154+
if user_profile is not None:
155+
file = user_profile.profile_picture
156+
157+
if file is None:
158+
raise HTTPException(
159+
status_code=status.HTTP_404_NOT_FOUND,
160+
detail=f"User {user_id} does not have a profile picture in the database."
161+
)
162+
163+
return Response(
164+
content=file,
165+
media_type="image/jpg",
166+
headers={
167+
"content-disposition": f"attachment; \
168+
filename={user_profile.first_name}_profile_picture.jpg"
169+
}
170+
)
171+
120172

121173
@router.get(
122174
"/users/profile/me/resume",
@@ -166,6 +218,54 @@ def get_user_resume_me(
166218
filename={current_user.username}_resume.pdf"
167219
}
168220
)
221+
222+
@router.get(
223+
"/users/profile/{user_id}/resume",
224+
status_code=status.HTTP_200_OK,
225+
tags=["UserProfile"],
226+
summary="Retrieve the resume of the user with id == user_id from the database.",
227+
description="Retrieve the specified users' resume from the database as a pdf file.",
228+
response_description="The specified users' resume."
229+
)
230+
def get_user_resume(db: Session = Depends(dependencies.get_db),
231+
user_id: int = Path()):
232+
"""
233+
GET route to obtain the resume of the user with id == user_id.
234+
235+
Parameters
236+
----------
237+
db: Session
238+
a database session
239+
user_id: int
240+
the unique identifier of the user
241+
242+
Returns
243+
-------
244+
fastapi.Response
245+
a response with the resume
246+
"""
247+
248+
dependencies.user_profile_exists(db, user_id)
249+
250+
user_profile = user_profile_crud.get_user_profile_by_id(db, user_id)
251+
252+
if user_profile is not None:
253+
file = user_profile.resume
254+
255+
if file is None:
256+
raise HTTPException(
257+
status_code=status.HTTP_404_NOT_FOUND,
258+
detail=f"User {user_id} does not have a resume in the database."
259+
)
260+
261+
return Response(
262+
content=file,
263+
media_type="application/pdf",
264+
headers={
265+
"content-disposition": f"attachment; \
266+
filename={user_profile.first_name}_resume.pdf"
267+
}
268+
)
169269

170270
@router.get(
171271
"/users/profile/{username}",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { useState } from "react";
2+
import { useEffect } from "react";
3+
import { Typography } from "@mui/material";
4+
import JobPost from "./JobPost";
5+
6+
const JobApply = () => {
7+
const [appliedJobs, setAppliedJobs] = useState([]);
8+
9+
const getAplliedJobs = async () => {
10+
await fetch("https://chapi.techstartucalgary.com/applications/me", {
11+
mode: "cors",
12+
headers: {
13+
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
14+
},
15+
})
16+
.then((response) => response.json())
17+
.then((data) => setAppliedJobs(data))
18+
.catch((error) => console.error(error));
19+
};
20+
21+
useEffect(() => {
22+
getAplliedJobs();
23+
}, []);
24+
25+
return (
26+
<div>
27+
{appliedJobs.length > 0 ? (
28+
<div>
29+
{appliedJobs.map((appliedJob) => (
30+
<JobPost
31+
key={appliedJob.id}
32+
job={appliedJob.job}
33+
disabled={true}
34+
status={appliedJob.application_status.status}
35+
/>
36+
))}
37+
</div>
38+
) : (
39+
<Typography>No found Job</Typography>
40+
)}
41+
</div>
42+
);
43+
};
44+
45+
export default JobApply;

frontend/src/components/JobPost.jsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React from "react";
2+
import { useState } from "react";
23
import { Button, Typography } from "@mui/material";
4+
import { Label } from "../components/RecruiterApplicantsComponents";
35

46
import "../styles/JobPost.css";
57

68
function JobPost(props) {
9+
const [apply, setApply] = useState(false);
10+
711
const formatCurrency = (num) => {
812
return num.toLocaleString("en-US", {
913
style: "currency",
@@ -12,6 +16,51 @@ function JobPost(props) {
1216
});
1317
};
1418

19+
const getStatusColor = (status) => {
20+
switch (status) {
21+
case "SUBMITTED":
22+
return "success";
23+
case "UNDER REVIEW":
24+
return "info";
25+
case "UNDERGOING FURTHER SCREENING":
26+
return "warning";
27+
case "REJECTED":
28+
return "error";
29+
case "OFFER SENT":
30+
return "offerSent";
31+
default:
32+
return "secondary";
33+
}
34+
};
35+
36+
const applyHandler = async () => {
37+
setApply(true);
38+
try {
39+
const response = await fetch(
40+
`https://chapi.techstartucalgary.com/applications/${props.job.id}`,
41+
{
42+
method: "POST",
43+
mode: "cors",
44+
headers: {
45+
"Content-Type": "application/json",
46+
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
47+
},
48+
},
49+
);
50+
if (response.ok) {
51+
alert("Job was succesful");
52+
setApply(false);
53+
window.location.reload();
54+
} else {
55+
throw new Error("Job application failed. Please try again later");
56+
}
57+
} catch (error) {
58+
console.error(error);
59+
alert(error.message);
60+
setApply(false);
61+
}
62+
};
63+
1564
return (
1665
<div className="jobContainer">
1766
<Typography className="jobTitle">{props.job.title}</Typography>
@@ -37,9 +86,23 @@ function JobPost(props) {
3786
</Typography>
3887
);
3988
})}
40-
<Button variant="contained" className="applyButton">
41-
<Typography>Apply</Typography>
42-
</Button>
89+
{!props.disabled ? (
90+
<Button
91+
variant="contained"
92+
href=""
93+
className="applyButton"
94+
disabled={apply}
95+
onClick={applyHandler}
96+
>
97+
{apply ? (
98+
<Typography>Applying...</Typography>
99+
) : (
100+
<Typography>Apply</Typography>
101+
)}
102+
</Button>
103+
) : (
104+
<Label color={getStatusColor(props.status)}>{props.status}</Label>
105+
)}
43106
</div>
44107
);
45108
}

frontend/src/pages/ApplicantHome.jsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,52 @@ import "../styles/ApplicantHome.css";
44
import JobList from "../components/JobList";
55
import { Button, Container, IconButton, Typography, Box } from "@mui/material";
66
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
7+
import JobApply from "../components/JobApply";
78

89
function ApplicantHome() {
910
const [showJobs, setShowJobs] = useState(false);
11+
const [appliedList, setAppliedList] = useState(false);
1012

1113
const toggleJobs = () => {
1214
setShowJobs(!showJobs);
1315
};
16+
const toggleAppliedJobs = () => {
17+
setAppliedList(!appliedList);
18+
};
1419

1520
return (
1621
<Box className="appHome">
1722
<Button variant="outlined" href="#/editProfile">
1823
<Typography>Update my Profile</Typography>
1924
</Button>
2025

21-
<Container className="row" sx={{ marginTop: "20px" }}>
22-
<Typography align="center" variant="h5">
23-
Job List
24-
</Typography>
25-
<IconButton className="toggleJobs" onClick={toggleJobs}>
26-
<ArrowDropDownIcon
27-
fontSize="large"
28-
className={showJobs ? "rotate" : ""}
29-
/>
30-
</IconButton>
31-
</Container>
26+
<Box className="column" sx={{ marginTop: "20px" }}>
27+
<div className="row">
28+
<Typography align="center" variant="h5">
29+
Jobs For You
30+
</Typography>
31+
<IconButton className="toggleJobs" onClick={toggleJobs}>
32+
<ArrowDropDownIcon
33+
fontSize="large"
34+
className={showJobs ? "rotate" : ""}
35+
/>
36+
</IconButton>
37+
</div>
38+
{showJobs && <JobList />}
3239

33-
{showJobs && <JobList />}
40+
<div className="row">
41+
<Typography align="left" variant="h5">
42+
Pending Jobs
43+
</Typography>
44+
<IconButton className="toggleJobs" onClick={toggleAppliedJobs}>
45+
<ArrowDropDownIcon
46+
fontSize="large"
47+
className={appliedList ? "rotate" : ""}
48+
/>
49+
</IconButton>
50+
</div>
51+
{appliedList && <JobApply />}
52+
</Box>
3453
</Box>
3554
);
3655
}

frontend/src/styles/JobPost.css

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
border-radius: 15px;
44
margin-bottom: 10px;
55
padding: 20px;
6-
width: 100%;
6+
width: 50% !important;
7+
flex-direction: column;
78
}
89

910
.jobContainer * {
@@ -37,3 +38,30 @@
3738
border-radius: 15px;
3839
padding: 20px;
3940
}
41+
42+
@media only screen and (max-width: 600px) {
43+
.jobContainer {
44+
display: flex;
45+
flex-direction: column;
46+
align-items: center;
47+
justify-content: space-between;
48+
width: 10% !important;
49+
}
50+
51+
.descContainer {
52+
padding: 5px !important;
53+
display: flex;
54+
align-items: center;
55+
box-sizing: border-box;
56+
}
57+
58+
.jobDescription {
59+
font-size: 10px !important;
60+
width: 100% !important;
61+
text-align: center !important;
62+
}
63+
.jobContainer * {
64+
margin-bottom: 15px;
65+
width: 50% !important ;
66+
}
67+
}

0 commit comments

Comments
 (0)