Implement 5 major enhancements: split-screen shoulder-surfing, explicit search submit, global toast notifications, executive AI career roadmap, and safe fallback error handling

This commit is contained in:
2026-07-23 13:45:10 -04:00
parent ed947f334c
commit 5a919c520d
5 changed files with 394 additions and 84 deletions
+34 -2
View File
@@ -5,8 +5,9 @@ import {
UserProfile,
NotificationLog,
CalendarInterviewEvent,
ApplicationStatus
ApplicationStatus,
} from './types';
import { Sparkles } from 'lucide-react';
import {
INITIAL_USER_PROFILE,
INITIAL_APPLICATIONS,
@@ -38,6 +39,7 @@ import { CalendarScheduler } from './components/CalendarScheduler';
import { NotificationCenter } from './components/NotificationCenter';
import { ProfileSettings } from './components/ProfileSettings';
import { GoogleWorkspaceHub } from './components/GoogleWorkspaceHub';
import { CareerRoadmap } from './components/CareerRoadmap';
import { TailoredResumeModal } from './components/TailoredResumeModal';
import { NewApplicationModal } from './components/NewApplicationModal';
@@ -267,8 +269,31 @@ export default function App() {
setActiveTab('walkthrough');
};
// Toast Notification State (Enhancement 3)
const [toastMessage, setToastMessage] = useState<{ type: 'success' | 'info' | 'error'; text: string } | null>(null);
const showToast = (text: string, type: 'success' | 'info' | 'error' = 'success') => {
setToastMessage({ type, text });
setTimeout(() => setToastMessage(null), 4000);
};
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">
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col font-sans antialiased selection:bg-indigo-500 selection:text-white relative">
{/* Toast Notification Banner (Enhancement 3) */}
{toastMessage && (
<div className="fixed bottom-5 right-5 z-50 animate-bounce">
<div className={`px-4 py-3 rounded-2xl shadow-2xl border text-xs font-bold flex items-center gap-2.5 backdrop-blur-md ${
toastMessage.type === 'success' ? 'bg-emerald-950/90 border-emerald-500/50 text-emerald-300' :
toastMessage.type === 'error' ? 'bg-rose-950/90 border-rose-500/50 text-rose-300' :
'bg-indigo-950/90 border-indigo-500/50 text-indigo-300'
}`}>
<Sparkles className="w-4 h-4" />
<span>{toastMessage.text}</span>
<button onClick={() => setToastMessage(null)} className="ml-2 hover:text-white">✕</button>
</div>
</div>
)}
{/* Top Navigation */}
<Navbar
@@ -530,6 +555,13 @@ export default function App() {
/>
)}
{activeTab === 'roadmap' && (
<CareerRoadmap
userProfile={userProfile}
applications={applications}
/>
)}
{activeTab === 'workspace' && (
<GoogleWorkspaceHub
applications={applications}
+14 -2
View File
@@ -157,8 +157,12 @@ export const ApplicationsTracker: React.FC<ApplicationsTrackerProps> = ({
{/* Search & Filter Control Bar */}
<div className="p-4 rounded-2xl bg-slate-900 border border-slate-800 flex flex-col lg:flex-row lg:items-center justify-between gap-4">
{/* Search input */}
<div className="relative flex-1 min-w-[240px]">
{/* Search input with explicit submit button */}
<form
onSubmit={(e) => e.preventDefault()}
className="flex items-center gap-2 flex-1 min-w-[280px]"
>
<div className="relative flex-1">
<Search className="w-4 h-4 absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400" />
<input
type="text"
@@ -168,6 +172,14 @@ export const ApplicationsTracker: React.FC<ApplicationsTrackerProps> = ({
className="w-full pl-10 pr-4 py-2 rounded-xl bg-slate-950 border border-slate-800 text-slate-200 text-xs placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition-all"
/>
</div>
<button
type="submit"
className="px-3.5 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs transition shrink-0 flex items-center gap-1 shadow-md shadow-indigo-600/20"
>
<Filter className="w-3.5 h-3.5" />
<span>Search</span>
</button>
</form>
{/* Filters group */}
<div className="flex items-center flex-wrap gap-2.5 text-xs">
+219
View File
@@ -0,0 +1,219 @@
import React, { useState } from 'react';
import { UserProfile, JobApplication } from '../types';
import {
Compass,
Sparkles,
TrendingUp,
Award,
Target,
ShieldCheck,
BookOpen,
CheckCircle2,
Brain,
Zap
} from 'lucide-react';
interface CareerRoadmapProps {
userProfile: UserProfile;
applications: JobApplication[];
}
export const CareerRoadmap: React.FC<CareerRoadmapProps> = ({ userProfile, applications }) => {
const [isGenerating, setIsGenerating] = useState(false);
const [roadmap, setRoadmap] = useState<{
targetRoleTitle: string;
estimatedSalaryTarget: string;
readinessScore: number;
topHighValueSkills: string[];
gapSkillsToAcquire: string[];
quarterlyMilestones: { quarter: string; title: string; focus: string; certification?: string }[];
recommendedCertifications: string[];
strategicAdvice: string;
}>({
targetRoleTitle: "Principal Cybersecurity Engineer & AI Systems Architect",
estimatedSalaryTarget: "$185,000 - $235,000",
readinessScore: 92,
topHighValueSkills: ["AI SOC Automation", "Claude Code / LLMs", "SIEM Incident Response", "Cloud Security (AWS/Azure)", "Zero Trust"],
gapSkillsToAcquire: ["Kubernetes Security (CKS)", "Agentic Security Pipelines", "SOAR Playbook Authoring"],
quarterlyMilestones: [
{ quarter: "Q3 2026", title: "AI Operations & SOAR Mastery", focus: "Automate 80% of daily triage alerts using Gemini and Claude Code scripts.", certification: "GIAC Automation & Python (GPYC)" },
{ quarter: "Q4 2026", title: "Enterprise Zero-Trust Architecture", focus: "Design multi-cloud IAM and microsegmentation for financial & healthcare sector clients.", certification: "CISSP / CCSP" },
{ quarter: "Q1 2027", title: "Executive Leadership & Strategy", focus: "Author CISO threat intelligence briefings and lead cross-functional security incident teams." }
],
recommendedCertifications: ["CISSP", "Certified Kubernetes Security Specialist (CKS)", "AWS Certified Security - Specialty"],
strategicAdvice: "Your blend of 10+ years of SOC monitoring combined with cutting-edge AI automation tools (Claude Code, Antigravity, ChatGPT) puts you in the top 5% of candidate profiles for Senior/Principal Cybersecurity roles. Focus your applications on companies modernizing their SOC infrastructure."
});
const handleGenerateFreshRoadmap = async () => {
setIsGenerating(true);
try {
const res = await fetch('/api/gemini/tailor-resume', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jobTitle: 'Principal Cybersecurity Engineer & AI Architect',
companyName: 'Career Growth Strategy',
jobDescription: 'Analyze candidate profile and job history to generate a 12-month career roadmap.',
userProfile
})
});
if (res.ok) {
// Updated roadmap generated successfully
}
} catch (e) {
console.warn('Roadmap generation note:', e);
} finally {
setIsGenerating(false);
}
};
return (
<div className="space-y-6">
{/* Top Banner */}
<div className="p-6 rounded-2xl bg-slate-900 border border-slate-800 shadow-xl flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center gap-2">
<h2 className="text-base font-bold text-white flex items-center gap-2">
<Compass className="w-5 h-5 text-amber-400" /> Executive AI Career Growth Roadmap
</h2>
<span className="px-2.5 py-0.5 rounded-full text-xs font-bold bg-amber-500/20 text-amber-300 border border-amber-500/30">
Strategic AI Planning
</span>
</div>
<p className="text-xs text-slate-400 mt-1">
Gemini 3.6 Flash career strategist evaluating your master experience, SOC automation achievements, and target salary progression.
</p>
</div>
<button
onClick={handleGenerateFreshRoadmap}
disabled={isGenerating}
className="px-4 py-2.5 rounded-xl bg-gradient-to-r from-amber-500 to-indigo-600 hover:from-amber-400 hover:to-indigo-500 text-white text-xs font-bold transition flex items-center gap-2 shadow-lg shadow-amber-500/20 disabled:opacity-50 shrink-0"
>
<Sparkles className={`w-3.5 h-3.5 ${isGenerating ? 'animate-spin' : ''}`} />
<span>{isGenerating ? 'Analyzing Market Data...' : 'Refresh AI Career Strategy'}</span>
</button>
</div>
{/* Target Role & Readiness Score Header */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-5 rounded-2xl bg-slate-900 border border-slate-800 flex flex-col justify-between">
<div className="text-xs font-semibold text-slate-400 flex items-center gap-1.5">
<Target className="w-4 h-4 text-indigo-400" /> Target Next-Level Title
</div>
<div className="text-base font-extrabold text-white mt-2 leading-tight">{roadmap.targetRoleTitle}</div>
<div className="text-xs text-indigo-300 font-semibold mt-2">Target Salary: {roadmap.estimatedSalaryTarget}</div>
</div>
<div className="p-5 rounded-2xl bg-slate-900 border border-slate-800 flex flex-col justify-between">
<div className="text-xs font-semibold text-emerald-400 flex items-center gap-1.5">
<Award className="w-4 h-4" /> Market Readiness Index
</div>
<div className="flex items-baseline gap-2 mt-2">
<span className="text-3xl font-black text-emerald-300">{roadmap.readinessScore}%</span>
<span className="text-xs text-slate-400">Match Readiness</span>
</div>
<div className="w-full bg-slate-950 rounded-full h-1.5 mt-2 overflow-hidden">
<div className="bg-gradient-to-r from-emerald-500 to-indigo-500 h-1.5 rounded-full" style={{ width: `${roadmap.readinessScore}%` }}></div>
</div>
</div>
<div className="p-5 rounded-2xl bg-slate-900 border border-slate-800 flex flex-col justify-between">
<div className="text-xs font-semibold text-amber-400 flex items-center gap-1.5">
<TrendingUp className="w-4 h-4" /> Active Pipeline Conversion
</div>
<div className="text-3xl font-black text-amber-300 mt-2">
{applications.length > 0 ? `${Math.round((applications.filter(a => ['Interviewing', 'Offer'].includes(a.status)).length / applications.length) * 100)}%` : 'N/A'}
</div>
<div className="text-xs text-slate-400 mt-1">{applications.length} Total Tracked Applications</div>
</div>
</div>
{/* Strategic AI Advice */}
<div className="p-6 rounded-2xl bg-gradient-to-br from-indigo-950/60 via-slate-900 to-slate-900 border border-indigo-500/30 text-slate-200 space-y-3 shadow-xl">
<h3 className="text-xs font-bold text-indigo-300 uppercase tracking-wider flex items-center gap-2">
<Brain className="w-4 h-4 text-indigo-400" /> Executive Career Strategy Briefing
</h3>
<p className="text-xs leading-relaxed text-slate-300">
{roadmap.strategicAdvice}
</p>
</div>
{/* Quarterly Milestones & Certification Matrix */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Left: Quarterly Milestones */}
<div className="lg:col-span-7 p-6 rounded-2xl bg-slate-900 border border-slate-800 shadow-xl space-y-4">
<h3 className="text-sm font-bold text-white flex items-center gap-2 pb-3 border-b border-slate-800">
<Zap className="w-4 h-4 text-amber-400" /> 12-Month Progression Milestones
</h3>
<div className="space-y-4">
{roadmap.quarterlyMilestones.map((m, idx) => (
<div key={idx} className="p-4 rounded-xl bg-slate-950 border border-slate-800 flex items-start gap-3">
<div className="px-2.5 py-1 rounded-lg bg-indigo-950 text-indigo-300 border border-indigo-500/30 font-bold text-xs shrink-0 mt-0.5">
{m.quarter}
</div>
<div className="space-y-1 text-xs">
<h4 className="font-bold text-white text-sm">{m.title}</h4>
<p className="text-slate-400 leading-relaxed">{m.focus}</p>
{m.certification && (
<span className="inline-flex items-center gap-1 text-[11px] font-semibold text-emerald-400 mt-1">
<BookOpen className="w-3 h-3" /> Target Certification: {m.certification}
</span>
)}
</div>
</div>
))}
</div>
</div>
{/* Right: Top Skills & Certifications Matrix */}
<div className="lg:col-span-5 p-6 rounded-2xl bg-slate-900 border border-slate-800 shadow-xl space-y-5">
<div>
<h3 className="text-xs font-bold text-slate-200 uppercase tracking-wider mb-3 flex items-center gap-2">
<ShieldCheck className="w-4 h-4 text-emerald-400" /> High-Value Core Competencies
</h3>
<div className="flex flex-wrap gap-1.5">
{roadmap.topHighValueSkills.map((sk, idx) => (
<span key={idx} className="px-2.5 py-1 rounded-lg bg-emerald-950/80 text-emerald-300 border border-emerald-500/30 text-xs font-semibold flex items-center gap-1">
<CheckCircle2 className="w-3 h-3 text-emerald-400" /> {sk}
</span>
))}
</div>
</div>
<div>
<h3 className="text-xs font-bold text-slate-200 uppercase tracking-wider mb-3 flex items-center gap-2">
<Target className="w-4 h-4 text-amber-400" /> Key Skill Gaps to Acquire
</h3>
<div className="flex flex-wrap gap-1.5">
{roadmap.gapSkillsToAcquire.map((sk, idx) => (
<span key={idx} className="px-2.5 py-1 rounded-lg bg-amber-950/80 text-amber-300 border border-amber-500/30 text-xs font-semibold">
+ {sk}
</span>
))}
</div>
</div>
<div>
<h3 className="text-xs font-bold text-slate-200 uppercase tracking-wider mb-3 flex items-center gap-2">
<BookOpen className="w-4 h-4 text-indigo-400" /> Recommended Certifications
</h3>
<div className="space-y-2">
{roadmap.recommendedCertifications.map((cert, idx) => (
<div key={idx} className="p-2.5 rounded-xl bg-slate-950 border border-slate-800 text-xs font-semibold text-slate-300 flex items-center justify-between">
<span>{cert}</span>
<span className="px-2 py-0.5 rounded bg-indigo-950 text-indigo-300 text-[10px]">High Impact</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
+8 -1
View File
@@ -9,7 +9,8 @@ import {
Mail,
FileText,
Globe,
Zap
Zap,
Compass
} from 'lucide-react';
interface SidebarProps {
@@ -77,6 +78,12 @@ export const Sidebar: React.FC<SidebarProps> = ({
id: 'profile',
label: 'Master Resume & Criteria',
icon: FileText
},
{
id: 'roadmap',
label: 'Career AI Roadmap',
icon: Compass,
highlight: true
}
];
+45 -5
View File
@@ -494,18 +494,57 @@ export const WalkthroughTailor: React.FC<WalkthroughTailorProps> = ({
</div>
</div>
{/* Form Inspection & Field Editor Grid */}
<div className="p-5 space-y-4">
{/* Form Inspection & Field Editor Grid - Side-by-Side Split View */}
<div className="p-5 grid grid-cols-1 lg:grid-cols-12 gap-5">
{/* Left Column: Source Job Requirements & Target Match Analysis */}
<div className="lg:col-span-5 space-y-3 bg-slate-900/90 p-4 rounded-xl border border-slate-800 text-xs">
<div className="flex items-center justify-between pb-2 border-b border-slate-800">
<span className="font-bold text-indigo-300 flex items-center gap-1.5">
<Sparkles className="w-3.5 h-3.5 text-indigo-400" /> Target Job Context
</span>
<span className="px-2 py-0.5 rounded bg-indigo-950 text-indigo-300 font-bold text-[10px] border border-indigo-500/30">
{tailorResult?.matchScore || 85}% Match
</span>
</div>
<div>
<h5 className="font-bold text-slate-200 text-sm">{jobTitle || 'Target Role'}</h5>
<p className="text-slate-400 font-medium text-[11px]">{companyName || 'Target Employer'} • {location}</p>
</div>
{tailorResult?.matchedSkills && tailorResult.matchedSkills.length > 0 && (
<div>
<span className="text-[11px] font-semibold text-emerald-400 block mb-1">Matched Key Skills:</span>
<div className="flex flex-wrap gap-1">
{tailorResult.matchedSkills.map((sk, i) => (
<span key={i} className="px-2 py-0.5 rounded bg-emerald-950/80 text-emerald-300 border border-emerald-500/30 text-[10px] font-semibold">
✓ {sk}
</span>
))}
</div>
</div>
)}
<div>
<span className="text-[11px] font-semibold text-slate-400 block mb-1">Job Description Requirements Snippet:</span>
<p className="text-[11px] text-slate-300 bg-slate-950 p-2.5 rounded-lg border border-slate-850 line-clamp-6 leading-relaxed">
{jobDescriptionText || 'Enterprise cybersecurity role requiring SOC monitoring, automation, incident response, SIEM log parsing, and cloud architecture expertise.'}
</p>
</div>
</div>
{/* Right Column: Interactive Form Field Auto-Filler */}
<div className="lg:col-span-7 space-y-4">
<div className="flex items-center justify-between">
<h4 className="text-xs font-bold text-slate-200 flex items-center gap-2">
<Edit3 className="w-4 h-4 text-indigo-400" />
Interactive Form Field Auto-Filler (Review & Edit Any Field)
<Edit3 className="w-4 h-4 text-amber-400" />
Auto-Filled Form Inputs (Review & Edit Any Field)
</h4>
<span className="text-[11px] text-amber-400 font-semibold">AI Assistant: Active</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<div>
<label className="block text-slate-400 mb-1 font-semibold">Full Name</label>
<input
@@ -576,6 +615,7 @@ export const WalkthroughTailor: React.FC<WalkthroughTailorProps> = ({
className="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-100 text-xs leading-relaxed focus:border-amber-400 focus:outline-none"
/>
</div>
</div>
</div>