diff --git a/frontend/jobsnap/src/components/ConfirmDeleteModal.js b/frontend/jobsnap/src/components/ConfirmDeleteModal.js index 54dc641..3905ece 100644 --- a/frontend/jobsnap/src/components/ConfirmDeleteModal.js +++ b/frontend/jobsnap/src/components/ConfirmDeleteModal.js @@ -1,24 +1,21 @@ import React, { useState } from 'react'; const ConfirmDeleteModal = ({ isOpen, onClose, onConfirm, cvId }) => { - if (!isOpen) return null; // Hide the modal if it's not open + if (!isOpen) return null; const handleConfirm = () => { - console.log("Confirm deleting CV with ID:", cvId); // Log the CV ID being deleted - onConfirm(cvId); // Pass the CV ID to confirm the deletion + console.log("Confirm deleting CV with ID:", cvId); + onConfirm(cvId); }; return (

Are you sure you want to delete this?

-
- - +
+

Are you sure you want to delete this CV?

+ +
diff --git a/frontend/jobsnap/src/components/Header.js b/frontend/jobsnap/src/components/Header.js index 8a0ed38..9d342a2 100644 --- a/frontend/jobsnap/src/components/Header.js +++ b/frontend/jobsnap/src/components/Header.js @@ -19,8 +19,8 @@ export default function Header() { const { user, logout } = useAuth(); // Accesăm datele utilizatorului și funcția de logout din context const navigate = useNavigate(); const handleLogout = () => { - logout(); // Call the logout function - navigate('/home'); // Redirect to the home page after logging out + logout(); + navigate('/home'); }; return (
diff --git a/frontend/jobsnap/src/components/Hero_Section.js b/frontend/jobsnap/src/components/Hero_Section.js index b7ed630..c83c1aa 100644 --- a/frontend/jobsnap/src/components/Hero_Section.js +++ b/frontend/jobsnap/src/components/Hero_Section.js @@ -1,7 +1,7 @@ 'use client' import React, { useContext } from 'react'; -import { Link } from 'react-router-dom'; // Importăm Link pentru navigare -import { AuthContext } from '../context/AuthContext'; // Importă contextul care conține informațiile despre utilizator +import { Link } from 'react-router-dom'; +import { AuthContext } from '../context/AuthContext'; export default function HeroSection() { const { user } = useContext(AuthContext); // Obținem utilizatorul din context @@ -32,7 +32,7 @@ export default function HeroSection() { {/* Afișează butonul doar dacă rolul utilizatorului este "student" */} {user && user.role === 'student' && ( Get started diff --git a/frontend/jobsnap/src/context/AuthContext.js b/frontend/jobsnap/src/context/AuthContext.js index 66f2d59..6cba36d 100644 --- a/frontend/jobsnap/src/context/AuthContext.js +++ b/frontend/jobsnap/src/context/AuthContext.js @@ -29,7 +29,7 @@ export const AuthProvider = ({ children }) => { } const data = await response.json(); - console.log('Server response:', data); // Verifică ce răspuns primești + console.log('Server response:', data); setUser(data); // Salvează utilizatorul în starea contextului localStorage.setItem("user", JSON.stringify(data)); // Salvează utilizatorul în localStorage } catch (error) { diff --git a/frontend/jobsnap/src/index.js b/frontend/jobsnap/src/index.js index 3c9a80e..7d62244 100644 --- a/frontend/jobsnap/src/index.js +++ b/frontend/jobsnap/src/index.js @@ -3,12 +3,12 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -import { AuthProvider } from './context/AuthContext'; // Importă AuthProvider +import { AuthProvider } from './context/AuthContext'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - {/* Adaugă AuthProvider pentru a împacheta aplicația */} + diff --git a/frontend/jobsnap/src/pages/CVBuilder.js b/frontend/jobsnap/src/pages/CVBuilder.js index 190f3d9..6d560a7 100644 --- a/frontend/jobsnap/src/pages/CVBuilder.js +++ b/frontend/jobsnap/src/pages/CVBuilder.js @@ -6,17 +6,17 @@ import html2pdf from 'html2pdf.js'; import axios from 'axios'; import { useAuth } from '../context/AuthContext'; import { ToastContainer, toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; // Importă stilurile pentru toast +import 'react-toastify/dist/ReactToastify.css'; export default function CVBuilder() { - const { cvType } = useParams(); // Preluăm tipul de CV din ruta curentă - const { user } = useAuth(); // Obținem user-ul din contextul de autentificare - const navigate = useNavigate(); // Folosim useNavigate pentru redirecționare după salvare + const { cvType } = useParams(); + const { user } = useAuth(); + const navigate = useNavigate(); - const notifySuccess = () => toast.success("CV a fost adăugat cu succes!"); - const notifyError = () => toast.error("A apărut o eroare la adăugarea CV-ului."); - // console.log("CV Type:", cvType); // Verifică în consola browser-ului dacă preiei corect tipul de CV + const notifySuccess = () => toast.success("The CV has been saved successfully!"); + const notifyError = () => toast.error("An error occurred while saving the CV."); + // console.log("CV Type:", cvType); const cvFields = { @@ -97,7 +97,7 @@ export default function CVBuilder() { const [formData, setFormData] = useState({}); const [image, setImage] = useState(null); - // Setăm câmpurile în funcție de tipul de CV selectat + useEffect(() => { console.log('CV Type:', cvType); if (cvFields[cvType]) { @@ -110,10 +110,10 @@ export default function CVBuilder() { console.log('Invalid cvType:', cvType); } - // Încarcă datele salvate la profil + axios.get('http://localhost:8080/api/cv-templates') .then(response => { - setFormData(response.data); // Salvează datele în starea componentelor + setFormData(response.data); }) .catch(error => { console.error('Error loading CV data:', error); @@ -136,35 +136,6 @@ export default function CVBuilder() { } }; - // const handleSaveCV = () => { - // const cvData = { ...formData, userId: user.id }; // Adăugăm userId - // console.log(cvData) - // // Verifică dacă există o imagine - // if (image) { - // cvData.image = image; // Adăugăm imaginea - // } - // - // fetch('http://localhost:8080/api/cv-templates', { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify(cvData), - // }) - // .then(response => { - // if (!response.ok) { - // throw new Error('Error saving CV'); - // } - // return response.json(); - // }) - // .then(data => { - // console.log('CV saved:', data); - // navigate('/profile'); // Redirecționează utilizatorul după salvare - // }) - // .catch(error => { - // console.error('Error:', error); - // }); - // }; const handleSaveCV = async () => { const cvData = { @@ -213,17 +184,15 @@ export default function CVBuilder() { // Redirecționează utilizatorul după 2 secunde setTimeout(() => { navigate('/start'); // Redirecționează la pagina de start - }, 3000); // 2000ms = 2 secunde + }, 3000); } catch (error) { console.error("Error adding cv:", error); - // Afișează notificare de eroare + notifyError(); } }; - - const handleDownloadPDF = () => { const element = document.getElementById('cv-preview'); const options = { @@ -232,14 +201,17 @@ export default function CVBuilder() { html2canvas: { scale: 3 }, }; + html2pdf().set(options).from(element).save(); }; + return ( -
+

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}

-
- )} +

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' && ( <>