-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
129 lines (115 loc) · 5.58 KB
/
App.tsx
File metadata and controls
129 lines (115 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import React, { useState, useCallback, useRef } from 'react';
import { OptimizedProfileData } from './types';
import { generateProfileData } from './services/geminiService';
import { LinkedInStager } from './components/LinkedInStager';
import { Button } from './components/common/Button';
import { Spinner } from './components/common/Spinner';
import { Header } from './components/layout/Header';
import { TutorialPage } from './components/TutorialPage'; // Import new TutorialPage
import { APP_NAME, TAGLINE, BYLINE, BYLINE_LINK, FOOTER_LOGO_PLACEHOLDER_URL } from './constants'; // Import constants
function App() {
const [resumeText, setResumeText] = useState<string>('');
const [optimizedProfile, setOptimizedProfile] = useState<OptimizedProfileData | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [showTutorialPage, setShowTutorialPage] = useState<boolean>(false); // New state for tutorial page
// Ref to LinkedInStager's download function
const linkedInStagerRef = useRef<{ handleDownloadPdf: () => void }>(null);
const handleOptimizeResume = useCallback(async () => {
if (!resumeText.trim()) {
setError("Please paste your resume text to optimize.");
return;
}
setIsLoading(true);
setError(null);
setOptimizedProfile(null);
try {
const data = await generateProfileData(resumeText);
setOptimizedProfile(data);
} catch (err: any) {
console.error("Failed to optimize resume:", err);
setError(err.message || "Failed to optimize resume. Please try again.");
} finally {
setIsLoading(false);
}
}, [resumeText]);
const handleResetApp = useCallback(() => {
setResumeText('');
setOptimizedProfile(null);
setError(null);
setIsLoading(false);
setShowTutorialPage(false); // Ensure tutorial is closed on reset
}, []);
const triggerPdfDownload = useCallback(() => {
if (linkedInStagerRef.current && optimizedProfile) {
linkedInStagerRef.current.handleDownloadPdf();
} else if (!optimizedProfile) {
alert("Please optimize your profile first to download the plan.");
}
}, [optimizedProfile]);
const toggleTutorialPage = useCallback(() => {
setShowTutorialPage((prev) => !prev);
}, []);
return (
<div className="min-h-screen flex flex-col items-center bg-gradient-to-br from-slate-900 to-gray-950 text-slate-100 font-sans">
<Header
appName={APP_NAME}
tagline={TAGLINE}
byline={BYLINE}
bylineLink={BYLINE_LINK}
onResetApp={handleResetApp}
onDownloadPdf={optimizedProfile ? triggerPdfDownload : undefined}
onToggleTutorial={toggleTutorialPage}
isTutorialActive={showTutorialPage}
/>
<main className="flex-grow w-full px-4 pt-48 pb-10 flex flex-col items-center"> {/* Adjusted pt- for fixed header */}
{showTutorialPage ? (
<TutorialPage onBackToApp={toggleTutorialPage} />
) : !optimizedProfile ? (
<div className="w-full max-w-3xl bg-slate-800 bg-opacity-70 backdrop-blur-sm p-6 md:p-8 rounded-xl shadow-2xl border border-orange-700 glow-border">
<h2 className="text-2xl md:text-3xl font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-amber-400 to-orange-500 text-glow text-center">Paste Your Resume</h2>
<p className="text-amber-200 mb-6 text-center text-sm md:text-base">
To get started, simply paste the raw text content of your resume into the box below. Our AI will then analyze, optimize, and structure it for maximum impact on job sites.
</p>
<textarea
className="w-full h-64 p-4 mb-6 bg-slate-700 border border-orange-600 rounded-lg text-slate-50 placeholder-slate-400 focus:ring-2 focus:ring-amber-500 focus:outline-none transition-all duration-300 resize-y"
placeholder="Paste your resume text here..."
value={resumeText}
onChange={(e) => setResumeText(e.target.value)}
disabled={isLoading}
aria-label="Resume text input"
></textarea>
<Button
onClick={handleOptimizeResume}
disabled={isLoading}
className="w-full py-3 text-lg bg-gradient-to-r from-orange-500 to-amber-600 hover:from-orange-600 hover:to-amber-700 hover:shadow-lg hover:shadow-orange-500/50"
aria-label="Optimize Profile button"
>
{isLoading ? <Spinner /> : 'Optimize Profile'}
</Button>
{error && (
<p className="mt-4 text-red-400 text-center" role="alert">{error}</p>
)}
<p className="mt-6 text-sm text-amber-300 text-center">
Your data is processed securely and not stored.
</p>
</div>
) : (
<LinkedInStager profileData={optimizedProfile} onReset={handleResetApp} ref={linkedInStagerRef} />
)}
</main>
<footer className="w-full py-6 text-amber-400 text-sm text-center border-t border-orange-800 mt-auto">
<div className="flex items-center justify-center">
<img
src={FOOTER_LOGO_PLACEHOLDER_URL}
alt={`${APP_NAME} Logo`}
className="h-[4em] w-[4em] mr-2"
aria-hidden="true"
/>
© {new Date().getFullYear()} {APP_NAME} <a href={BYLINE_LINK} target="_blank" rel="noopener noreferrer" className="text-orange-400 hover:text-orange-300 transition-colors duration-200 ml-1">{BYLINE}</a>. All rights reserved.
</div>
</footer>
</div>
);
}
export default App;