+
Start Building Your {cvType ? cvType.charAt(0).toUpperCase() + cvType.slice(1) : 'CV'} CV
-
+
@@ -285,8 +257,8 @@ export default function CVBuilder() {
{/* Preview Section */}
-
@@ -298,6 +270,13 @@ export default function CVBuilder() {
Save CV
+
+
);
diff --git a/frontend/jobsnap/src/pages/CVDetailPage.js b/frontend/jobsnap/src/pages/CVDetailPage.js
index a97ea54..1159df0 100644
--- a/frontend/jobsnap/src/pages/CVDetailPage.js
+++ b/frontend/jobsnap/src/pages/CVDetailPage.js
@@ -1,9 +1,11 @@
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
+import CVTemplate from "../components/CVTemplate";
const CVDetailPage = () => {
- const { cvId } = useParams(); // Get cvId from URL
+ const { cvId } = useParams(); // Obținem cvId din URL
const [cv, setCv] = useState(null);
+ const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
const fetchCVDetails = async () => {
@@ -26,9 +28,12 @@ const CVDetailPage = () => {
return
Loading...
;
}
- // Function to check if the field has valid content (not "N/A" or empty)
- const hasValidContent = (field) => {
- return field && field !== 'N/A';
+ const handleViewCV = () => {
+ setIsModalOpen(true);
+ };
+
+ const handleCloseCV = () => {
+ setIsModalOpen(false);
};
return (
@@ -44,54 +49,90 @@ const CVDetailPage = () => {
)}
- {/* Conditionally render fields based on content */}
- {hasValidContent(cv.email) && (
-
-
Contact Information
-
Email: {cv.email}
-
Phone: {cv.phone}
-
- )}
+ {/* Detalii CV */}
+
+
Contact Information
+
Email: {cv.email}
+
Phone: {cv.phone}
- {hasValidContent(cv.education) && (
-
-
Education
-
{cv.education}
-
- )}
+
Education
+
{cv.education}
- {hasValidContent(cv.experience) && (
-
-
Professional Experience
-
{cv.experience}
-
- )}
+
Experience
+
{cv.experience}
- {hasValidContent(cv.skills) && (
-
- )}
+
Skills
+
{cv.skills}
- {hasValidContent(cv.technologies) && (
-
-
Technologies
-
{cv.technologies}
-
- )}
+ {cv.cvType === 'it' && (
+ <>
+
Technologies
+
{cv.technologies}
+ >
+ )}
- {hasValidContent(cv.certifications) && (
-
-
Certifications
-
{cv.certifications}
-
- )}
+ {cv.cvType === 'business' && (
+ <>
+
Business Skills
+
{cv.skills}
+ >
+ )}
+
+ {cv.cvType === 'marketing' && (
+ <>
+
Marketing Skills
+
{cv.skills}
+ >
+ )}
+
+ {cv.cvType === 'graphicdesign' && (
+ <>
+
Design Skills
+
{cv.skills}
+ >
+ )}
+
+ {cv.cvType === 'healthcare' && (
+ <>
+
Healthcare Skills
+
{cv.skills}
+ >
+ )}
+
+ {cv.cvType === 'education' && (
+ <>
+
Degree
+
{cv.degree}
+ >
+ )}
+
+ {/* Render Projects and Certifications for any CV type */}
+
Projects
+
{cv.projects}
+
+
Certifications
+
{cv.certifications}
+
+
+ {/* Show CV Template in Modal */}
+
- {hasValidContent(cv.projects) && (
-
-
Projects
-
{cv.projects}
+ {isModalOpen && (
+
)}
diff --git a/frontend/jobsnap/src/pages/EditCV.js b/frontend/jobsnap/src/pages/EditCV.js
index c79a62c..f8528ed 100644
--- a/frontend/jobsnap/src/pages/EditCV.js
+++ b/frontend/jobsnap/src/pages/EditCV.js
@@ -5,6 +5,7 @@ import axios from 'axios';
import { useAuth } from '../context/AuthContext';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
+import html2pdf from "html2pdf.js";
export default function CVEdit() {
const { cvId } = useParams();
@@ -94,11 +95,11 @@ export default function CVEdit() {
};
useEffect(() => {
- // Încarcă datele CV-ului din backend pe baza cvId
+
axios.get(`http://localhost:8080/api/cv/${cvId}`)
.then(response => {
- setFormData(response.data); // Setează datele CV-ului
- setCvType(response.data.cvType); // Preia tipul de CV din răspunsul backend
+ setFormData(response.data);
+ setCvType(response.data.cvType);
if (response.data.imagePath) {
setImage(response.data.imagePath);
}
@@ -107,18 +108,18 @@ export default function CVEdit() {
console.error('Error loading CV data:', error);
alert('A apărut o eroare la încărcarea datelor CV-ului.');
});
- }, [cvId]); // Dependința corectă pentru încărcarea datelor doar la început
+ }, [cvId]);
useEffect(() => {
if (cvType && cvFields[cvType]) {
- // Setează doar câmpurile relevante pentru tipul de CV
+
const fields = cvFields[cvType].reduce((acc, field) => {
- acc[field.name] = formData[field.name] || ''; // Preia valorile din formData
+ acc[field.name] = formData[field.name] || '';
return acc;
}, {});
setFormData(fields);
}
- }, [cvType]); // Actualizează doar când cvType se schimbă
+ }, [cvType]);
const handleChange = (e) => {
const { name, value } = e.target;
@@ -151,19 +152,34 @@ export default function CVEdit() {
});
notifySuccess1();
- setImage(response.data.imagePath); // Actualizează imaginea cu calea din răspuns
- // Redirecționează utilizatorul după 2 secunde
+ setImage(response.data.imagePath);
+
setTimeout(() => {
- navigate('/profile'); // Redirecționează la pagina de start
- }, 3000); // 2000ms = 2 secunde
+ navigate('/profile');
+ }, 3000);
} catch (error) {
console.error("Error updating cv:", error);
notifyError1();
}
};
+
+ const handleDownloadPDF = () => {
+ const element = document.getElementById('cv-preview');
+ const options = {
+ filename: `${formData.fullName || 'My-CV'}.pdf`,
+ jsPDF: { unit: 'pt', format: 'a4' },
+ html2canvas: { scale: 3 },
+ };
+
+
+ html2pdf().set(options).from(element).save();
+ };
+
+
return (
-
+
Edit Your CV
@@ -219,8 +235,8 @@ export default function CVEdit() {
{/* Preview Section */}
-
@@ -233,7 +249,15 @@ export default function CVEdit() {
Save CV
-
+
+
+
+
);
}
diff --git a/frontend/jobsnap/src/pages/LogIn.js b/frontend/jobsnap/src/pages/LogIn.js
index f03829a..f6c04a6 100644
--- a/frontend/jobsnap/src/pages/LogIn.js
+++ b/frontend/jobsnap/src/pages/LogIn.js
@@ -9,7 +9,7 @@ import { ToastContainer, toast } from 'react-toastify';
export default function LoginPage() {
- const { login } = useAuth(); // Obținem funcția de login din context
+ const { login } = useAuth();
const navigate = useNavigate();
const [email, setEmail] = useState('');
@@ -29,7 +29,7 @@ export default function LoginPage() {
const data = await response.json();
if (response.ok) {
- // Save the user data, including the role, to localStorage
+
console.log('DATA:');
console.log(JSON.stringify(data));
localStorage.setItem('user', JSON.stringify(data));
diff --git a/frontend/jobsnap/src/pages/Profile.css b/frontend/jobsnap/src/pages/Profile.css
index fd63d51..e244a70 100644
--- a/frontend/jobsnap/src/pages/Profile.css
+++ b/frontend/jobsnap/src/pages/Profile.css
@@ -53,16 +53,16 @@
}
.cv-template {
- overflow-wrap: break-word; /* Permite întreruperea cuvintelor pe linia următoare */
- word-wrap: break-word; /* Asigură-te că se întrerupe pe cuvinte mari */
- word-break: break-word; /* Întrerupe cuvintele prea lungi */
- max-width: 100%; /* Asigură-te că containerul se ajustează la lățimea disponibilă */
- font-size: 0.9rem; /* Reduce fontul pentru a economisi spațiu */
- padding: 10px; /* Reducere padding pentru carduri mai mici */
- margin: 10px 0; /* Margine mai mică între carduri */
- max-height: 500px; /* Maximizează înălțimea pentru a preveni cardurile prea mari */
- overflow-y: auto; /* Adaugă scroll dacă conținutul depășește înălțimea */
- box-sizing: border-box; /* Asigură-te că padding-ul și marginile sunt incluse în dimensiunile elementului */
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+ max-width: 100%;
+ font-size: 0.9rem;
+ padding: 10px;
+ margin: 10px 0;
+ max-height: 500px;
+ overflow-y: auto;
+ box-sizing: border-box;
}
@@ -139,7 +139,6 @@ button:focus {
}
-/* Save button */
.save-button {
background-color: #4CAF50; /* Green */
color: white;
@@ -147,7 +146,7 @@ button:focus {
}
.save-button:hover {
- background-color: #45a049; /* Darker green when hovered */
+ background-color: #45a049;
}
/* Cancel button */
@@ -160,34 +159,31 @@ button:focus {
-/* Lista de CV-uri folosind un grid cu 1 coloană pe ecrane mici, 2 pe ecrane medii și 3 pe ecrane mari */
.cv-list {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 coloane pe ecranele mari */
gap: 30px; /* Spațiu între cardurile de CV */
margin-top: 20px;
padding: 0 20px;
- justify-items: center; /* Centrează cardurile pe orizontală */
+ justify-items: center;
}
/* Cardurile de CV */
.cv-card {
background-color: #fff;
- padding: 20px; /* Mărim padding-ul pentru a face cardul mai generos */
+ padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 1px solid #ddd;
text-align: center;
- width: 100%; /* Cardurile vor ocupa toată lățimea disponibilă */
- max-width: 500px; /* Limităm lățimea cardului */
+ width: 100%;
+ max-width: 500px;
/*max-height: 500px; */
- margin: 0 auto; /* Centrează cardurile pe axa orizontală */
+ margin: 0 auto;
transition: transform 0.3s ease, box-shadow 0.3s ease;
margin-bottom: 20px;
}
-.cv-preview{
-}
.cv-card:hover {
transform: translateY(-8px);
@@ -212,13 +208,13 @@ button:focus {
/* Butoanele din fiecare card de CV */
.cv-actions {
display: flex;
- flex-wrap: wrap; /* Permite butoanelor să se mute pe rânduri în cazul în care nu se potrivesc pe un singur rând */
- justify-content: center; /* Centrează butoanele pe orizontală */
- gap: 10px; /* Spațiu între butoane */
- width: 100%; /* Asigură-te că butoanele ocupă întreaga lățime disponibilă */
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 10px;
+ width: 100%;
}
-/* Stilizarea butoanelor din carduri */
+
.cv-actions button {
background-color: #4CAF50;
color: white;
@@ -227,38 +223,38 @@ button:focus {
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
- width: 100%; /* Asigură-te că butoanele nu ies din card */
- max-width: 120px; /* Limităm lățimea maximă a butoanelor pentru a se potrivi în mod corespunzător */
- box-sizing: border-box; /* Asigură-te că padding-ul nu face ca butoanele să iasă din container */
+ width: 100%;
+ max-width: 120px;
+ box-sizing: border-box;
}
-/* Stilizare la hover pentru butoane */
+
.cv-actions button:hover {
opacity: 0.8;
}
-/* Media Queries pentru responsive design - 1 coloană pe ecranele mici */
+
@media (max-width: 768px) {
.cv-list {
- grid-template-columns: 1fr; /* 1 coloană pe ecranele mici */
+ grid-template-columns: 1fr;
}
.cv-card {
- max-width: 100%; /* Permite cardurilor să ocupe toată lățimea */
+ max-width: 100%;
}
.cv-actions button {
- width: 100%; /* Pe ecranele mici, butoanele vor ocupa întreaga lățime */
+ width: 100%;
}
}
-/* Media Queries pentru responsive design - 3 coloane pe ecranele mari */
+
@media (min-width: 1024px) {
.cv-list {
- grid-template-columns: repeat(3, 1fr); /* 3 coloane pe ecranele mari */
+ grid-template-columns: repeat(3, 1fr);
}
.cv-actions button {
- width: 48%; /* Pe ecranele mari, butoanele vor ocupa doar 48% din lățimea cardului */
+ width: 48%;
}
}
diff --git a/frontend/jobsnap/src/pages/Profile.js b/frontend/jobsnap/src/pages/Profile.js
index 2de8d32..63db060 100644
--- a/frontend/jobsnap/src/pages/Profile.js
+++ b/frontend/jobsnap/src/pages/Profile.js
@@ -18,10 +18,8 @@ const Profile = () => {
const [updatedProfile, setUpdatedProfile] = useState({});
const [cvList, setCvList] = useState([]);
const navigate = useNavigate();
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [cvToDelete, setCvToDelete] = useState(null); // Stochează CV-ul care urmează să fie șters
- const [cvToDownload, setCvToDownload] = useState(null); // Manage the CV to download
- const [isDownloading, setIsDownloading] = useState(false); // Manage visibility when downloading
+ const [cvToDelete, setCvToDelete] = useState(null);
+
const notifySuccess = () => toast.success("The CV has been deleted successfully!");
@@ -45,6 +43,7 @@ const Profile = () => {
try {
console.log("User role:", user.role);
// Verificăm rolul utilizatorului pentru a alege endpoint-ul corect
+
const endpoint = user.role === 'student'
? `http://localhost:8080/api/students/${user.id}`
: `http://localhost:8080/api/employers/${user.id}`;
@@ -106,7 +105,7 @@ const Profile = () => {
};
const handleUploadRedirect2 = () => {
- navigate('/upload-cv-page'); // Redirecționează la pagina de upload pentru studenți
+ navigate('/upload-cv-page'); // Redirecționează la pagina de upload pentru employer
};
const handleUploadRedirect3 = () => {
@@ -168,36 +167,27 @@ const Profile = () => {
html2pdf().set(options).from(element).save();
};
- const handleDeleteCV = (cvId) => {
- console.log("CV to delete:", cvId); // Log the specific CV ID you are deleting
- setCvToDelete(cvId); // Save the selected CV ID to delete
- setIsModalOpen(true); // Open the modal for that specific CV
- };
-
+ const handleDeleteCV = async (cvId) => {
- const confirmDelete = async (cvId) => {
try {
+
const response = await axios.delete(`http://localhost:8080/api/cv/${cvId}`);
+
if (response.status === 200 || response.status === 204) {
- setCvList(cvList.filter(cv => cv.id !== cvId)); // Remove the deleted CV from the list
- notifySuccess(); // Show success toast
+
+ setCvList(cvList.filter(cv => cv.id !== cvId));
+ notifySuccess();
} else {
throw new Error('Failed to delete CV');
}
} catch (error) {
console.error("Error deleting CV:", error);
- notifyError(); // Show error toast
- } finally {
- setIsModalOpen(false); // Close the modal
- setCvToDelete(null); // Reset the CV to delete
+ notifyError();
}
};
- const cancelDelete = () => {
- setIsModalOpen(false); // Close the modal without deleting
- setCvToDelete(null); // Reset the CV to delete
- };
+
if (!user) {
@@ -243,7 +233,7 @@ const Profile = () => {
/>
- {/* Render different fields based on the user role */}
+
{user.role === 'student' && (
<>