diff --git a/.app.pid b/.app.pid index ba62ac8..d6929ee 100644 --- a/.app.pid +++ b/.app.pid @@ -1 +1 @@ -274566 +275088 diff --git a/server.ts b/server.ts index a5826de..0638c2d 100644 --- a/server.ts +++ b/server.ts @@ -221,7 +221,8 @@ Extract the following fields accurately and output JSON: }; const { text, providerUsed } = await generateTextWithLLMFallback(prompt, schema, llmConfig); - const parsedData = JSON.parse(text || "{}"); + const cleanJson = (text || "{}").replace(/```json/gi, '').replace(/```/g, '').trim(); + const parsedData = JSON.parse(cleanJson || "{}"); res.json({ success: true, data: parsedData, providerUsed }); } catch (error: any) { console.error("Error parsing job URL:", error); @@ -455,8 +456,8 @@ Candidate Criteria: - Excluded Companies: ${excludedCompanies.join(", ")} Candidate Profile: -- Skills: ${userProfile.skills?.join(", ")} -- Summary: ${userProfile.summary} +- Skills: ${userProfile?.skills?.join(", ") || "Cybersecurity, Python, Incident Response"} +- Summary: ${userProfile?.summary || "Cybersecurity & IT Specialist"} List of Jobs to Evaluate: ${JSON.stringify(jobFeeds)} @@ -943,8 +944,11 @@ app.post("/api/local/data", (req, res) => { try { store = JSON.parse(fs.readFileSync(DATA_FILE, "utf-8") || "{}"); } catch (e) {} } if (key) { - if (items !== undefined) store[key] = { items }; - if (profile !== undefined) store[key] = { profile }; + store[key] = { + ...(store[key] || {}), + ...(items !== undefined && { items }), + ...(profile !== undefined && { profile }) + }; } fs.writeFileSync(DATA_FILE, JSON.stringify(store, null, 2)); res.json({ success: true }); diff --git a/src/App.tsx b/src/App.tsx index e79a57c..9749ede 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -90,30 +90,42 @@ export default function App() { // Handlers const handleUpdateStatus = async (id: string, newStatus: ApplicationStatus, note?: string) => { + const previousApps = [...applications]; setApplications(prev => prev.map(a => a.id === id ? { ...a, status: newStatus } : a)); - await updateApplicationStatus(id, newStatus, note); + try { + await updateApplicationStatus(id, newStatus, note); - // Send email alert for important status updates - if (['Interviewing', 'Offer', 'Screening'].includes(newStatus)) { - const app = applications.find(a => a.id === id); - if (app) { - const notif = await saveNotificationLog({ - timestamp: new Date().toISOString(), - type: 'STATUS_CHANGE', - recipient: userProfile.email, - subject: `Application Status Alert: ${app.company} — ${newStatus}`, - body: `Your job application for ${app.role} at ${app.company} has moved to ${newStatus}.`, - status: 'SENT', - platform: 'Gmail' - }); - setNotifications(prev => [notif, ...prev]); + // Send email alert for important status updates + if (['Interviewing', 'Offer', 'Screening'].includes(newStatus)) { + const app = previousApps.find(a => a.id === id); + if (app) { + const notif = await saveNotificationLog({ + timestamp: new Date().toISOString(), + type: 'STATUS_CHANGE', + recipient: userProfile.email, + subject: `Application Status Alert: ${app.company} — ${newStatus}`, + body: `Your job application for ${app.role} at ${app.company} has moved to ${newStatus}.`, + status: 'SENT', + platform: 'Gmail' + }); + setNotifications(prev => [notif, ...prev]); + } } + } catch (err) { + console.error('Failed to persist status update, rolling back:', err); + setApplications(previousApps); } }; const handleDeleteApplication = async (id: string) => { + const previousApps = [...applications]; setApplications(prev => prev.filter(a => a.id !== id)); - await deleteApplicationRecord(id); + try { + await deleteApplicationRecord(id); + } catch (err) { + console.error('Failed to delete application, rolling back:', err); + setApplications(previousApps); + } }; const handleSaveApplicationRecord = async (appData: Omit) => { diff --git a/src/components/ApplicationsTracker.tsx b/src/components/ApplicationsTracker.tsx index 3ce17cb..17ddc76 100644 --- a/src/components/ApplicationsTracker.tsx +++ b/src/components/ApplicationsTracker.tsx @@ -269,7 +269,7 @@ export const ApplicationsTracker: React.FC = ({
{app.role} {app.autoApplied && ( - + AI )} diff --git a/src/components/WalkthroughTailor.tsx b/src/components/WalkthroughTailor.tsx index 2380e35..d38b03f 100644 --- a/src/components/WalkthroughTailor.tsx +++ b/src/components/WalkthroughTailor.tsx @@ -77,6 +77,23 @@ export const WalkthroughTailor: React.FC = ({ customCoverLetterText: '' }); + // Keep surfingFormData reactively synchronized with userProfile and tailorResult updates + React.useEffect(() => { + setSurfingFormData(prev => ({ + ...prev, + fullName: userProfile.fullName || prev.fullName, + email: userProfile.email || prev.email, + phone: userProfile.phone || prev.phone, + linkedin: userProfile.linkedinUrl || prev.linkedin, + workAuthorization: userProfile.eeoDeclarations?.workAuthorization || prev.workAuthorization, + veteranStatus: userProfile.eeoDeclarations?.veteranStatus === 'PROTECTED_VETERAN' ? 'Protected Veteran' : 'Not a Veteran', + disabilityStatus: userProfile.eeoDeclarations?.disabilityStatus === 'YES_DISABILITY' ? 'Yes, Disability' : 'No Disability', + clearanceLevel: userProfile.eeoDeclarations?.clearanceLevel || prev.clearanceLevel, + tailoredBulletsText: tailorResult?.tailoredBullets?.join('\n• ') || prev.tailoredBulletsText, + customCoverLetterText: tailorResult?.coverLetter || prev.customCoverLetterText + })); + }, [userProfile, tailorResult]); + // Step 1: Parse Job posting details using Gemini const handleParsePosting = async () => { if (!jobUrl && !jobDescriptionText) return; diff --git a/src/lib/googleAuth.ts b/src/lib/googleAuth.ts index d5b8191..ac8be3a 100644 --- a/src/lib/googleAuth.ts +++ b/src/lib/googleAuth.ts @@ -65,8 +65,16 @@ export const googleWorkspaceSignIn = async (silentMode: boolean = false): Promis throw new Error('Popup window was blocked by your browser. Please allow popups for http://localhost:3000.'); } + const startTime = Date.now(); const interval = setInterval(async () => { try { + if (Date.now() - startTime > 60000) { + clearInterval(interval); + try { popup.close(); } catch (e) {} + reject(new Error('Sign-in process timed out after 60 seconds.')); + return; + } + if (popup.closed) { clearInterval(interval); if (!cachedAccessToken) {