Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 51 additions & 11 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import EliteHeader from "./Components/EliteHeader";
import FileUpload from "./Components/FileUpload";
import EnhancementViewer from "./Components/EnhancementViewer";
import DownloadButton from "./Components/DownloadButton";
import './i18n';



import "./i18n";

export default function App() {
const [resumeFile, setResumeFile] = useState(null);
const [enhancedText, setEnhancedText] = useState("");
const [base64FileContent, setBase64FileContent] = useState("");
const [fileFormat, setFileFormat] = useState("");
// Refs for triggering actions
const uploadButtonRef = useRef(null);
const enhanceButtonRef = useRef(null);
const downloadButtonRef = useRef(null);

// New central states for quota error handling
const [errorData, setErrorData] = useState({
Expand All @@ -36,6 +37,38 @@ export default function App() {
}
}, [resetTimer, isButtonDisabled]);

// Keyboard shortcuts: Ctrl+U (Upload), Ctrl+E (Enhance), Ctrl+D (Download)
useEffect(() => {
function handleKeyDown(e) {
if (e.ctrlKey && !e.shiftKey && !e.altKey) {
// Prevent browser default for these keys
if (["u", "e", "d"].includes(e.key.toLowerCase())) {
e.preventDefault();
}
if (e.key.toLowerCase() === "u" && !resumeFile) {
// Focus or click upload
if (uploadButtonRef.current) uploadButtonRef.current.click();
} else if (
e.key.toLowerCase() === "e" &&
resumeFile &&
enhanceButtonRef.current
) {
enhanceButtonRef.current.click();
} else if (
e.key.toLowerCase() === "d" &&
resumeFile &&
enhancedText &&
base64FileContent &&
downloadButtonRef.current
) {
downloadButtonRef.current.click();
}
}
}
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [resumeFile, enhancedText, base64FileContent]);

// Quota lock visual animation
const QuotaErrorBanner = () =>
errorData.isQuota ? (
Expand All @@ -53,10 +86,14 @@ export default function App() {
<div className="w-full px-4 sm:px-6 md:px-8">
<EliteHeader />
</div>

<main className="flex-1 w-full max-w-2xl mx-auto flex flex-col items-center justify-center px-4 sm:px-6 md:px-8 py-4 sm:py-6">
{!resumeFile ? (
<FileUpload setResumeFile={setResumeFile} />
<FileUpload
setResumeFile={setResumeFile}
uploadButtonRef={uploadButtonRef}
shortcutLabel="Ctrl + U"
/>
) : (
<div className="w-full space-y-4">
{/* Reset button for mobile */}
Expand All @@ -69,32 +106,35 @@ export default function App() {
}}
className="text-sm text-blue-600 hover:text-blue-800 underline mb-2"
>
Upload different file
 Upload different file
</button>

<EnhancementViewer
resumeFile={resumeFile}
setEnhancedText={setEnhancedText}
setBase64FileContent={setBase64FileContent}
setFileFormat={setFileFormat}
// Pass new error-related props
errorData={errorData}
setErrorData={setErrorData}
isButtonDisabled={isButtonDisabled}
setIsButtonDisabled={setIsButtonDisabled}
setResetTimer={setResetTimer}
enhanceButtonRef={enhanceButtonRef}
shortcutLabel="Ctrl + E"
/>

{enhancedText && base64FileContent && (
<DownloadButton
enhancedFileBase64={base64FileContent}
fileFormat={fileFormat}
downloadButtonRef={downloadButtonRef}
shortcutLabel="Ctrl + D"
/>
)}
</div>
)}
</main>

<footer className="text-xs sm:text-sm text-blue-800 py-4 font-medium opacity-80 text-center px-4">
© 2025 | AI Resume Enhancer SPA
</footer>
Expand Down
69 changes: 42 additions & 27 deletions src/Components/DownloadButton.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useTranslation } from "react-i18next";

export default function DownloadButton({
export default function DownloadButton({
enhancedFileBase64,
fileFormat
fileFormat,
downloadButtonRef,
shortcutLabel,
}) {
const { t } = useTranslation();
const { t } = useTranslation();

// Helper to convert base64 to Blob and trigger download
const downloadFileFromBase64 = (base64Data, fileName, mimeType) => {
Expand All @@ -17,7 +19,7 @@ export default function DownloadButton({
const blob = new Blob([byteArray], { type: mimeType });

const url = URL.createObjectURL(blob);
const link = document.createElement('a');
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
Expand All @@ -35,42 +37,55 @@ export default function DownloadButton({
mimeType = "application/pdf";
break;
case "docx":
mimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
mimeType =
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
break;
case "txt":
mimeType = "text/plain";
break;
default:
mimeType = "application/octet-stream";
}
downloadFileFromBase64(enhancedFileBase64, `enhanced_resume.${ext}`, mimeType);
downloadFileFromBase64(
enhancedFileBase64,
`enhanced_resume.${ext}`,
mimeType
);
} else {
alert(t("download.noFile"));
alert(t("download.noFile"));
}
};

return (
<div className="text-center w-full px-4 sm:px-0">
<button
onClick={handleDownload}
className="w-full sm:w-auto mt-4 sm:mt-6 px-6 sm:px-8 py-3 sm:py-4 text-base sm:text-lg font-bold text-white bg-blue-700 rounded-xl shadow-lg hover:bg-blue-800 active:scale-95 transition-all duration-200 flex items-center justify-center gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 sm:h-6 sm:w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
<div className="relative group inline-block w-full sm:w-auto">
<button
ref={downloadButtonRef}
onClick={handleDownload}
className="w-full sm:w-auto mt-4 sm:mt-6 px-6 sm:px-8 py-3 sm:py-4 text-base sm:text-lg font-bold text-white bg-blue-700 rounded-xl shadow-lg hover:bg-blue-800 active:scale-95 transition-all duration-200 flex items-center justify-center gap-2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
<span className="break-words">{t("download.button")}</span>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 sm:h-6 sm:w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
<span className="break-words">{t("download.button")}</span>
</button>
{shortcutLabel && (
<span className="absolute -top-6 left-1/2 -translate-x-1/2 bg-blue-100 text-blue-700 text-xs px-2 py-0.5 rounded shadow border border-blue-200 select-none opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity pointer-events-none">
{shortcutLabel}
</span>
)}
</div>
</div>
);
}
}
Loading