| @@ -0,0 +1,181 @@ | |||
| .uploadCV-main-container { | |||
| display: flex; | |||
| width: 100%; | |||
| height: 100%; | |||
| background-color: gray; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| .uploadCV-container { | |||
| display: flex; | |||
| width: 512px; | |||
| height: 591px; | |||
| background-color: #fff; | |||
| border-radius: 18px; | |||
| padding: 36px; | |||
| flex-direction: column; | |||
| } | |||
| .uploadCV-top-container { | |||
| display: flex; | |||
| align-items: center; | |||
| height: fit-content; | |||
| justify-content: space-between; | |||
| } | |||
| .uploadCV-top-container-image1 { | |||
| width: 18px; | |||
| height: 16.88px; | |||
| } | |||
| .uploadCV-top-container-image2 { | |||
| width: 9px; | |||
| height: 10.5px; | |||
| margin-left: 54px; | |||
| } | |||
| .uploadCV-top-container-text { | |||
| margin-left: 9px; | |||
| } | |||
| .uploadCV-top-container-text > p:first-child { | |||
| font-family: Source Sans Pro; | |||
| font-size: 24px; | |||
| font-weight: 600; | |||
| line-height: 32px; | |||
| letter-spacing: 0.02em; | |||
| text-align: left; | |||
| } | |||
| .uploadCV-top-container-text > p:last-child { | |||
| font-family: Source Sans Pro; | |||
| font-size: 16px; | |||
| font-weight: 600; | |||
| line-height: 20px; | |||
| letter-spacing: 0em; | |||
| text-align: left; | |||
| color: #226cb0; | |||
| } | |||
| .uploadCV-content-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| margin-top: 36px; | |||
| } | |||
| .uploadCV-input { | |||
| width: 100%; | |||
| height: 140px; | |||
| border: 1px solid #e4e4e4; | |||
| border-radius: 6px; | |||
| margin-top: 6px; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| .uploadCV-content-sub-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| .uploadCV-content-sub-container > p { | |||
| font-family: Source Sans Pro; | |||
| font-size: 14px; | |||
| font-weight: 400; | |||
| line-height: 18px; | |||
| letter-spacing: 0em; | |||
| text-align: left; | |||
| } | |||
| .uploadCV-input-sub-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .uploadCV-input-sub-container img { | |||
| width: 22px; | |||
| height: 22px; | |||
| } | |||
| .uploadCV-input-sub-container p { | |||
| width: 332px; | |||
| height: 40px; | |||
| font-family: Source Sans Pro; | |||
| font-size: 16px; | |||
| font-weight: 400; | |||
| line-height: 20.11px; | |||
| letter-spacing: 0em; | |||
| text-align: center; | |||
| } | |||
| .uploadCV-input2 { | |||
| width: 100%; | |||
| height: 140px; | |||
| border: 1px solid #e4e4e4; | |||
| border-radius: 6px; | |||
| margin-top: 6px; | |||
| padding: 15px; | |||
| resize: none; | |||
| } | |||
| .uploadCV-input2::placeholder { | |||
| color: #9d9d9d; | |||
| font-family: Source Sans Pro; | |||
| font-size: 14px; | |||
| font-style: italic; | |||
| font-weight: 400; | |||
| line-height: 18px; | |||
| letter-spacing: 0em; | |||
| text-align: left; | |||
| word-break: break-all; | |||
| white-space: normal; | |||
| } | |||
| .uploadCV-buttons-container { | |||
| display: flex; | |||
| margin-top: 35px; | |||
| justify-content: space-between; | |||
| flex-direction: row; | |||
| } | |||
| .uploadCV-button { | |||
| width: 202px; | |||
| height: 51px; | |||
| display: flex; | |||
| flex-direction: row; | |||
| justify-content: center; | |||
| align-items: center; | |||
| gap: 10; | |||
| border-radius: 9px; | |||
| border: none; | |||
| font-family: "Source Sans Pro"; | |||
| font-style: normal; | |||
| font-size: 12px; | |||
| line-height: 15px; | |||
| letter-spacing: 0.04em; | |||
| text-transform: uppercase; | |||
| color: #ffffff; | |||
| cursor: pointer; | |||
| } | |||
| .uploadCV-buttons-container > button:first-child { | |||
| @extend .uploadCV-button; | |||
| background: #ffffff; | |||
| color: #226cb0; | |||
| border: 1px solid #226cb0; | |||
| padding: 18px 72px; | |||
| font-weight: 600; | |||
| } | |||
| .uploadCV-buttons-container > button:last-child { | |||
| @extend .uploadCV-button; | |||
| background: #226cb0; | |||
| color: #ffffff; | |||
| padding: 18px 22px; | |||
| border: 1px solid #226cb0; | |||
| } | |||
| @@ -28,7 +28,7 @@ const ApplyForAd = ({ open, title, adId, onCloseModal }) => { | |||
| const [email, setEmail] = useState(""); | |||
| const [bitBucketLink, setBitBucketLink] = useState(""); | |||
| const [coverLetter, setCoverLetter] = useState(""); | |||
| const [pdfFile, setPdfFile] = useState(""); | |||
| const [pdfFile, setPdfFile] = useState(null); | |||
| const technologies = useSelector(selectTechnologies); | |||
| const dispatch = useDispatch(); | |||
| @@ -77,7 +77,7 @@ const ApplyForAd = ({ open, title, adId, onCloseModal }) => { | |||
| bitBucketLink, | |||
| email, | |||
| coverLetter, | |||
| pdfFile: "PDF", | |||
| pdfFile, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| @@ -1,7 +1,6 @@ | |||
| import React, { useRef } from "react"; | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import uploadIcon from "../../assets/images/upload.png"; | |||
| import { useDropzone } from "react-dropzone"; | |||
| const ApplyForAdFourthStage = ({ | |||
| coverLetter, | |||
| @@ -11,27 +10,13 @@ const ApplyForAdFourthStage = ({ | |||
| onDecreaseStage, | |||
| onFinishedFourStages, | |||
| }) => { | |||
| const inputFile = useRef(); | |||
| const { getRootProps, getInputProps } = useDropzone({ | |||
| onDrop: (acceptedFiles) => setPdfFile(acceptedFiles), | |||
| }); | |||
| const [dropzoneActive, setDropzoneActive] = useState(false); | |||
| const disabled = pdfFile === "" || coverLetter === ""; | |||
| const disabled = pdfFile === null || coverLetter === ""; | |||
| const handleFileUpload = (e) => { | |||
| const { files } = e.target; | |||
| if (files && files.length) { | |||
| // const filename = files[0].name; | |||
| // var parts = filename.split("."); | |||
| // const fileType = parts[parts.length - 1]; | |||
| setPdfFile(files[0]); | |||
| } | |||
| }; | |||
| const onButtonClick = () => { | |||
| inputFile.current.click(); | |||
| const handleDrop = (e) => { | |||
| e.preventDefault(); | |||
| setPdfFile(e.dataTransfer.files[0]); | |||
| }; | |||
| return ( | |||
| @@ -39,27 +24,56 @@ const ApplyForAdFourthStage = ({ | |||
| <div className="apply-for-ad-modal-form-control"> | |||
| <label>CV</label> | |||
| <div | |||
| className="apply-for-ad-modal-form-control-drag-and-drop" | |||
| {...getRootProps({ | |||
| className: "apply-for-ad-modal-form-control-drag-and-drop", | |||
| })} | |||
| className="uploadCV-input" | |||
| onDragOver={(e) => { | |||
| setDropzoneActive(true); | |||
| e.preventDefault(); | |||
| }} | |||
| onDragLeave={(e) => { | |||
| setDropzoneActive(false); | |||
| e.preventDefault(); | |||
| }} | |||
| onDrop={(e) => handleDrop(e)} | |||
| style={{ | |||
| backgroundColor: dropzoneActive ? "#F4F4F4" : "#ffffff", | |||
| }} | |||
| > | |||
| <img src={uploadIcon} alt="upload" /> | |||
| <input | |||
| {...getInputProps()} | |||
| style={{ display: "none" }} | |||
| accept=".pdf" | |||
| ref={inputFile} | |||
| onChange={handleFileUpload} | |||
| type="file" | |||
| /> | |||
| <p> | |||
| Prevuci .pdf dokument u ovom delu ekrana ili{" "} | |||
| <button style={{ cursor: "pointer" }} onClick={onButtonClick}> | |||
| Pretraži | |||
| </button>{" "} | |||
| na računaru | |||
| </p> | |||
| <div className="uploadCV-input-sub-container"> | |||
| <img src={uploadIcon} /> | |||
| <div className="uploadCV-input-sub-container"> | |||
| {pdfFile !== null ? ( | |||
| <p>{pdfFile.name}</p> | |||
| ) : ( | |||
| <> | |||
| <p> | |||
| Prevuci .pdf dokument u ovom delu ekrana ili | |||
| <label | |||
| htmlFor="upload-file" | |||
| style={{ | |||
| cursor: "pointer", | |||
| textDecoration: "underline", | |||
| color: "#1E92D0", | |||
| }} | |||
| > | |||
| Pretrazi | |||
| </label> | |||
| na racunaru | |||
| </p> | |||
| <input | |||
| type="file" | |||
| name="photo" | |||
| id="upload-file" | |||
| style={{ display: "none", zIndex: -1 }} | |||
| value={pdfFile} | |||
| onChange={(e) => { | |||
| console.log("Ovde smo"); | |||
| setPdfFile(e.target.files[0]); | |||
| }} | |||
| /> | |||
| </> | |||
| )} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className="apply-for-ad-modal-form-control"> | |||
| @@ -195,12 +195,8 @@ const CandidatesPage = ({ history }) => { | |||
| ) : ( | |||
| <AdsCandidatesPage history={history} search={search} /> | |||
| )} | |||
| <Fade in={isCVDisplayed} timeout={500} className="candidates-cv"> | |||
| <iframe | |||
| id="iframepdf" | |||
| src={linkToCV} | |||
| style={{ border: "0px" }} | |||
| ></iframe> | |||
| <Fade in={isCVDisplayed} timeout={400} className="candidates-cv"> | |||
| <embed src={`data:application/pdf;base64,${linkToCV}`} /> | |||
| </Fade> | |||
| </div> | |||
| </div> | |||
| @@ -12,6 +12,7 @@ import { | |||
| selectCandidates, | |||
| selectPagination, | |||
| } from "../../store/selectors/candidatesSelectors"; | |||
| import { getCV } from "../../request/candidatesRequest"; | |||
| const TableViewPage = ({ | |||
| history, | |||
| @@ -24,7 +25,7 @@ const TableViewPage = ({ | |||
| }) => { | |||
| const dispatch = useDispatch(); | |||
| const candidates = useSelector(selectCandidates); | |||
| const pagination = useSelector(selectPagination); // pagination is total number of candidates on backend | |||
| const pagination = useSelector(selectPagination); | |||
| const theme = useTheme(); | |||
| const matches = useMediaQuery(theme.breakpoints.down("361")); | |||
| @@ -89,17 +90,17 @@ const TableViewPage = ({ | |||
| ); | |||
| }; | |||
| const onHoverEnter = (e,linkToCV) => { | |||
| e.stopPropagation() | |||
| const changedLinkToCV = linkToCV.replace("view", "preview"); | |||
| setLinkToCV(changedLinkToCV); | |||
| setIsCVDisplayed(true); | |||
| const onClickCV = (e, linkToCV) => { | |||
| e.stopPropagation(); | |||
| console.log(linkToCV); | |||
| getCV("638077305621281656.pdf") | |||
| .then((res) => { | |||
| setLinkToCV(res.data); | |||
| setIsCVDisplayed(true); | |||
| }) | |||
| .catch((e) => console.log(e)); | |||
| }; | |||
| // const onHoverLeave = () => { | |||
| // setIsCVDisplayed(false); | |||
| // }; | |||
| return ( | |||
| <div className="candidates-table"> | |||
| <div style={{ overflowX: "auto", marginLeft: matches ? 36 : 72 }}> | |||
| @@ -132,7 +133,6 @@ const TableViewPage = ({ | |||
| cursor: "pointer", | |||
| }} | |||
| onClick={() => navigate(candidate.applicantId)} | |||
| // onMouseLeave={onHoverLeave} | |||
| > | |||
| <td> | |||
| {( | |||
| @@ -160,19 +160,13 @@ const TableViewPage = ({ | |||
| </td> | |||
| <td>{candidate.position}</td> | |||
| <td> | |||
| <a | |||
| href={candidate.CV} | |||
| <span | |||
| onClick={(e) => onClickCV(e, candidate.cv)} | |||
| className="cvLink" | |||
| onClick={(e) => | |||
| onHoverEnter( | |||
| e, | |||
| "https://drive.google.com/file/d/1nhhRwitNmeAgetmCEBYlr1YNqaxhXJoD/view" | |||
| ) | |||
| } | |||
| > | |||
| {candidate.firstName} | |||
| {candidate.lastName}.pdf | |||
| </a> | |||
| </span> | |||
| </td> | |||
| </tr> | |||
| ))} | |||
| @@ -63,5 +63,6 @@ export default { | |||
| }, | |||
| applicant: { | |||
| applyForAd: base + "/applicants/apply-for-ad", | |||
| getCV: base + "/applicants/get-CV", | |||
| }, | |||
| }; | |||
| @@ -1,4 +1,4 @@ | |||
| import { deleteRequest, getRequest, postRequest } from "."; | |||
| import { deleteRequest, getRequest, postRequest,downloadPdf } from "."; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const getFilteredCandidates = (payload) => { | |||
| @@ -63,3 +63,5 @@ export const getCandidateOptions = () => | |||
| export const initializeProcessRequest = (payload) => | |||
| postRequest(apiEndpoints.candidates.initProcess, payload); | |||
| export const getCV = (fileName) => downloadPdf(apiEndpoints.applicant.getCV + "?fileName=" + fileName); | |||
| @@ -29,6 +29,9 @@ export const deleteRequest = (url, params = null, options = null) => | |||
| export const downloadRequest = (url, params = null, options = null) => | |||
| request.get(url, { params, ...options, responseType: 'blob' }); | |||
| export const downloadPdf = (url, params = null, options = null) => | |||
| request.get(url, { params, ...options, responseType: 'application/pdf' }); | |||
| export const replaceInUrl = (url, pathVariables = {}) => { | |||
| const keys = Object.keys(pathVariables); | |||
| if (!keys.length) { | |||
| @@ -14,7 +14,21 @@ export function* applyForAdSaga({ payload }) { | |||
| try { | |||
| // const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| // yield call(addHeaderToken, JwtToken); | |||
| const { data } = yield call(applyForAdRequest, payload); | |||
| const formData = new FormData(); | |||
| formData.append("adId", payload.adId); | |||
| formData.append("firstName", payload.firstName); | |||
| formData.append("lastName", payload.lastName); | |||
| formData.append("dateOfBirth", payload.dateOfBirth); | |||
| formData.append("phoneNumber", payload.phoneNumber); | |||
| formData.append("technologiesIds", payload.technologiesIds); | |||
| formData.append("experience", payload.experience); | |||
| formData.append("linkedinLink", payload.linkedinLink); | |||
| formData.append("githubLink", payload.githubLink); | |||
| formData.append("bitBucketLink", payload.bitBucketLink); | |||
| formData.append("email", payload.email); | |||
| formData.append("coverLetter", payload.coverLetter); | |||
| formData.append("pdfFile", payload.pdfFile); | |||
| const { data } = yield call(applyForAdRequest, formData); | |||
| yield put(applyForAd(data)); | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||