diff --git a/frontend/src/pages/Enhance.jsx b/frontend/src/pages/Enhance.jsx index 4f5ecd064..cc7959ded 100644 --- a/frontend/src/pages/Enhance.jsx +++ b/frontend/src/pages/Enhance.jsx @@ -644,7 +644,6 @@ for (const part of parts) { } finally { setEnhancing(false) } - } } const handleGeneratePortfolio = async () => { diff --git a/frontend/src/pages/JobAlerts.jsx b/frontend/src/pages/JobAlerts.jsx index 460ed5de9..e7d24d72a 100644 --- a/frontend/src/pages/JobAlerts.jsx +++ b/frontend/src/pages/JobAlerts.jsx @@ -16,8 +16,8 @@ import { import toast from 'react-hot-toast'; import { jobAlertsApi, jobsApi } from '../services/api'; import { JobAlertModal, JobAlertsList } from '../components'; -import { SkeletonStatCards, SkeletonJobList } from '../components/ui/Skeleton' - +import { SkeletonStatCards, SkeletonJobList } from '../components/ui/Skeleton'; +import { isValidEmail } from '../utils/email'; export default function JobAlerts() { const [activeTab, setActiveTab] = useState('alerts'); // 'alerts' | 'search' const [isModalOpen, setIsModalOpen] = useState(false); @@ -280,11 +280,31 @@ function JobCard({ job, index }) { } }; - const handleEmail = () => { - if (job.recruiterEmail) { - window.location.href = `mailto:${job.recruiterEmail}?subject=Application for ${job.title}`; - } - }; +const [showMailtoConfirm, setShowMailtoConfirm] = useState(false); +const recruiterEmailIsValid = isValidEmail(job.recruiterEmail); + +const handleEmail = () => { + handleMailtoFallback(); +}; + +const handleMailtoFallback = () => { + if (!recruiterEmailIsValid) { + toast.error("That recruiter email doesn't look valid, so we can't open your mail client."); + return; + } + setShowMailtoConfirm(true); +}; + +const confirmMailtoFallback = () => { + setShowMailtoConfirm(false); + if (!isValidEmail(job.recruiterEmail)) { + toast.error("That recruiter email doesn't look valid, so we can't open your mail client."); + return; + } + const safeEmail = job.recruiterEmail.trim(); + const safeSubject = encodeURIComponent(`Application for ${job.title || 'this role'}`); + window.location.href = `mailto:${safeEmail}?subject=${safeSubject}`; +}; return ( )} + + {recruiterEmailIsValid && ( + + )} + + {showMailtoConfirm && ( +
+
+

+ Open your mail client to apply? +

+

+ This will leave Career Pilot and open your default email app with the recruiter's address pre-filled. +

+
+ + +
+
+
+ )}
); } \ No newline at end of file diff --git a/frontend/src/utils/email.js b/frontend/src/utils/email.js new file mode 100644 index 000000000..37338f57f --- /dev/null +++ b/frontend/src/utils/email.js @@ -0,0 +1,15 @@ +const MAX_EMAIL_LENGTH = 254; +const EMAIL_REGEX = + /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/; + +export function isValidEmail(email) { + if (typeof email !== 'string') return false; + + const trimmed = email.trim(); + + if (trimmed.length === 0 || trimmed.length > MAX_EMAIL_LENGTH) { + return false; + } + + return EMAIL_REGEX.test(trimmed); +} \ No newline at end of file