Initial commit

This commit is contained in:
2026-07-22 17:00:56 -04:00
commit 0f2d6bb738
33 changed files with 6997 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
import React, { useState, useEffect } from 'react';
import {
JobApplication,
JobFeedItem,
UserProfile,
NotificationLog,
CalendarInterviewEvent,
ApplicationStatus
} from './types';
import {
INITIAL_USER_PROFILE,
INITIAL_APPLICATIONS,
INITIAL_JOB_FEEDS,
INITIAL_CALENDAR_EVENTS,
INITIAL_NOTIFICATIONS
} from './data/mockData';
import {
fetchApplications,
saveApplication,
updateApplicationStatus,
deleteApplicationRecord,
fetchUserProfile,
saveUserProfile,
fetchNotifications,
saveNotificationLog,
fetchCalendarEvents,
saveCalendarEvent
} from './lib/firebase';
import { Navbar } from './components/Navbar';
import { Sidebar } from './components/Sidebar';
import { ApplicationsTracker } from './components/ApplicationsTracker';
import { WalkthroughTailor } from './components/WalkthroughTailor';
import { AutonomousEngine } from './components/AutonomousEngine';
import { JobFeeds } from './components/JobFeeds';
import { AnalyticsDashboard } from './components/AnalyticsDashboard';
import { CalendarScheduler } from './components/CalendarScheduler';
import { NotificationCenter } from './components/NotificationCenter';
import { ProfileSettings } from './components/ProfileSettings';
import { GoogleWorkspaceHub } from './components/GoogleWorkspaceHub';
import { TailoredResumeModal } from './components/TailoredResumeModal';
import { NewApplicationModal } from './components/NewApplicationModal';
export default function App() {
const [activeTab, setActiveTab] = useState<string>('applications');
// App state
const [applications, setApplications] = useState<JobApplication[]>(INITIAL_APPLICATIONS);
const [userProfile, setUserProfile] = useState<UserProfile>(INITIAL_USER_PROFILE);
const [jobFeeds, setJobFeeds] = useState<JobFeedItem[]>(INITIAL_JOB_FEEDS);
const [calendarEvents, setCalendarEvents] = useState<CalendarInterviewEvent[]>(INITIAL_CALENDAR_EVENTS);
const [notifications, setNotifications] = useState<NotificationLog[]>(INITIAL_NOTIFICATIONS);
// Modals & UI States
const [isAutoApplying, setIsAutoApplying] = useState(false);
const [selectedAppForModal, setSelectedAppForModal] = useState<JobApplication | null>(null);
const [isNewAppModalOpen, setIsNewAppModalOpen] = useState(false);
const [walkthroughInitialUrl, setWalkthroughInitialUrl] = useState('');
// Initial Load from Firestore
useEffect(() => {
async function loadData() {
try {
const firestoreApps = await fetchApplications();
if (firestoreApps.length > 0) setApplications(firestoreApps);
const profile = await fetchUserProfile();
if (profile) setUserProfile(profile);
const events = await fetchCalendarEvents();
if (events.length > 0) setCalendarEvents(events);
const notifs = await fetchNotifications();
if (notifs.length > 0) setNotifications(notifs);
} catch (err) {
console.warn('Initial load fallback to initial dataset:', err);
}
}
loadData();
}, []);
// Handlers
const handleUpdateStatus = async (id: string, newStatus: ApplicationStatus, note?: string) => {
setApplications(prev => prev.map(a => a.id === id ? { ...a, status: newStatus } : a));
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]);
}
}
};
const handleDeleteApplication = async (id: string) => {
setApplications(prev => prev.filter(a => a.id !== id));
await deleteApplicationRecord(id);
};
const handleSaveApplicationRecord = async (appData: Omit<JobApplication, 'id'>) => {
const saved = await saveApplication(appData);
setApplications(prev => [saved, ...prev.filter(a => a.id !== saved.id)]);
// Notification alert
const notif = await saveNotificationLog({
timestamp: new Date().toISOString(),
type: 'AUTO_APPLY',
recipient: userProfile.email,
subject: `Application Logged: ${saved.company} (${saved.role})`,
body: `Successfully logged application for ${saved.role} at ${saved.company} via ${saved.platform}. Match score: ${saved.matchScore}%.`,
status: 'SENT',
platform: 'Gmail'
});
setNotifications(prev => [notif, ...prev]);
};
const handleSaveUserProfile = async (updated: UserProfile) => {
setUserProfile(updated);
await saveUserProfile(updated);
};
const handleToggleAutoApply = () => {
const updated = {
...userProfile,
autoApplyCriteria: {
...userProfile.autoApplyCriteria,
enabled: !userProfile.autoApplyCriteria.enabled
}
};
handleSaveUserProfile(updated);
};
// Run Autonomous Auto-Apply Engine Batch
const handleRunAutoApplyBatch = async () => {
setIsAutoApplying(true);
try {
const res = await fetch('/api/gemini/auto-apply-run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jobFeeds,
userProfile,
criteria: userProfile.autoApplyCriteria
})
});
const data = await res.json();
if (data.success && Array.isArray(data.results)) {
for (const item of data.results) {
if (item.autoApplied) {
const feed = jobFeeds.find(f => f.id === item.jobId);
await handleSaveApplicationRecord({
company: item.company,
role: item.role,
platform: feed?.platform || 'Greenhouse',
location: feed?.location || 'San Francisco, CA',
salaryRange: feed?.salary || '$180,000 - $220,000',
status: 'Applied',
matchScore: item.matchScore,
appliedAt: new Date().toISOString(),
jobUrl: feed?.url,
jobDescription: feed?.description,
notes: item.reason,
autoApplied: true,
timeline: [{ date: new Date().toLocaleDateString(), action: 'Autonomous AI Auto-Applied' }]
});
}
}
}
} catch (err) {
console.error('Autonomous auto-apply failed:', err);
} finally {
setIsAutoApplying(false);
}
};
// Schedule Interview
const handleScheduleInterview = async (eventData: Omit<CalendarInterviewEvent, 'id'>) => {
const res = await fetch('/api/calendar/schedule-interview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...eventData, userEmail: userProfile.email })
});
const data = await res.json();
const savedEvent = await saveCalendarEvent({
...eventData,
googleCalendarEventId: data.googleCalendarEventId,
syncedToGoogle: true
});
setCalendarEvents(prev => [...prev.filter(e => e.id !== savedEvent.id), savedEvent]);
// Update application status to Interviewing
if (eventData.applicationId) {
await handleUpdateStatus(eventData.applicationId, 'Interviewing', `Interview scheduled for ${new Date(eventData.startTime).toLocaleDateString()}`);
}
// Log notification
const notif = await saveNotificationLog({
timestamp: new Date().toISOString(),
type: 'INTERVIEW_SCHEDULED',
recipient: userProfile.email,
subject: `Google Calendar Synced: ${eventData.company} Interview`,
body: `Interview for ${eventData.role} at ${eventData.company} scheduled for ${new Date(eventData.startTime).toLocaleString()}. Synced to Google Calendar.`,
status: 'SENT',
platform: 'Google Calendar & Gmail'
});
setNotifications(prev => [notif, ...prev]);
};
// Send test email
const handleSendTestEmail = async (recipient: string, subject: string, body: string) => {
const res = await fetch('/api/notifications/send-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ recipient, subject, body })
});
const data = await res.json();
const notif = await saveNotificationLog({
timestamp: new Date().toISOString(),
type: 'SYSTEM',
recipient,
subject,
body,
status: 'SENT',
platform: 'Gmail'
});
setNotifications(prev => [notif, ...prev]);
};
// Navigate & Tailor
const handleNavigateToWalkthrough = (jobUrl?: string) => {
if (jobUrl) setWalkthroughInitialUrl(jobUrl);
setActiveTab('walkthrough');
};
return (
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col font-sans antialiased selection:bg-indigo-500 selection:text-white">
{/* Top Navigation */}
<Navbar
activeTab={activeTab}
autoApplyEnabled={userProfile.autoApplyCriteria.enabled}
onToggleAutoApply={handleToggleAutoApply}
onOpenNewAppModal={() => setIsNewAppModalOpen(true)}
onRunAutoApplyNow={handleRunAutoApplyBatch}
isAutoApplying={isAutoApplying}
/>
{/* Main Container */}
<div className="flex-1 flex flex-col md:flex-row max-w-7xl w-full mx-auto">
{/* Left Sidebar */}
<Sidebar
activeTab={activeTab}
setActiveTab={setActiveTab}
pendingCount={applications.filter(a => ['Applied', 'Screening'].includes(a.status)).length}
autoAppliedCount={applications.filter(a => a.autoApplied).length}
/>
{/* Main Workspace Area */}
<main className="flex-1 p-4 sm:p-6 overflow-y-auto">
{activeTab === 'applications' && (
<ApplicationsTracker
applications={applications}
onUpdateStatus={handleUpdateStatus}
onDeleteApplication={handleDeleteApplication}
onViewTailoredResume={(app) => setSelectedAppForModal(app)}
onOpenNewAppModal={() => setIsNewAppModalOpen(true)}
onNavigateToWalkthrough={handleNavigateToWalkthrough}
/>
)}
{activeTab === 'walkthrough' && (
<WalkthroughTailor
userProfile={userProfile}
initialJobUrl={walkthroughInitialUrl}
onSaveTailoredApplication={handleSaveApplicationRecord}
/>
)}
{activeTab === 'auto-apply' && (
<AutonomousEngine
userProfile={userProfile}
jobFeeds={jobFeeds}
autoAppliedApps={applications.filter(a => a.autoApplied)}
onSaveProfile={handleSaveUserProfile}
onRunAutoApplyBatch={handleRunAutoApplyBatch}
isAutoApplying={isAutoApplying}
/>
)}
{activeTab === 'feeds' && (
<JobFeeds
jobFeeds={jobFeeds}
onTailorJobFeed={(item) => handleNavigateToWalkthrough(item.url)}
onQuickApplyFeed={async (item) => {
await handleSaveApplicationRecord({
company: item.company,
role: item.title,
platform: item.platform,
location: item.location,
salaryRange: item.salary,
status: 'Applied',
matchScore: item.estimatedMatch,
appliedAt: new Date().toISOString(),
jobUrl: item.url,
jobDescription: item.description,
autoApplied: true,
timeline: [{ date: new Date().toLocaleDateString(), action: 'Quick AI Auto-Applied from feed' }]
});
}}
/>
)}
{activeTab === 'analytics' && (
<AnalyticsDashboard applications={applications} />
)}
{activeTab === 'calendar' && (
<CalendarScheduler
events={calendarEvents}
applications={applications}
onScheduleInterview={handleScheduleInterview}
userEmail={userProfile.email}
/>
)}
{activeTab === 'notifications' && (
<NotificationCenter
notifications={notifications}
onSendTestEmail={handleSendTestEmail}
userEmail={userProfile.email}
/>
)}
{activeTab === 'profile' && (
<ProfileSettings
userProfile={userProfile}
onSaveProfile={handleSaveUserProfile}
/>
)}
{activeTab === 'workspace' && (
<GoogleWorkspaceHub
applications={applications}
userEmail={userProfile.email}
userName={userProfile.fullName}
/>
)}
</main>
</div>
{/* Tailored Resume Inspection Modal */}
{selectedAppForModal && (
<TailoredResumeModal
application={selectedAppForModal}
onClose={() => setSelectedAppForModal(null)}
/>
)}
{/* New Application Modal */}
{isNewAppModalOpen && (
<NewApplicationModal
onClose={() => setIsNewAppModalOpen(false)}
onSaveApplication={handleSaveApplicationRecord}
/>
)}
</div>
);
}