feat: Complete Smart Resume Formatter with R2 and Gemini AI integration
Some checks failed
Profile Linker Docker Build / Build and push Docker image (push) Failing after 3s
Some checks failed
Profile Linker Docker Build / Build and push Docker image (push) Failing after 3s
- Integrated Cloudflare R2 for template storage and converted file management - Added Google Gemini AI for resume parsing and HTML generation - Created backend API endpoints for templates, conversion, and history - Refactored frontend to use real API instead of mock data - Fixed Docker networking issues (IPv6/IPv4) for R2 connectivity - Added resumeService.ts for frontend API integration - Updated Vite configuration for proper asset serving in Docker - Successfully tested with 13 templates from R2 bucket
This commit is contained in:
69
frontend/components/HeaderBar.tsx
Normal file
69
frontend/components/HeaderBar.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { useState, FormEvent } from 'react';
|
||||
import { Search, RefreshCw, X } from 'lucide-react';
|
||||
|
||||
interface HeaderBarProps {
|
||||
onSearch: (term: string) => void;
|
||||
onRefresh: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const HeaderBar: React.FC<HeaderBarProps> = ({ onSearch, onRefresh, isLoading }) => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const handleSearch = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSearch(inputValue);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setInputValue('');
|
||||
onSearch('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm p-4 rounded-lg shadow-lg mb-8 sticky top-4 z-10 border border-slate-200 dark:border-slate-700">
|
||||
<form onSubmit={handleSearch} className="flex flex-col md:flex-row items-center gap-4">
|
||||
<div className="relative w-full">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search files by name..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
className="w-full pl-10 pr-10 py-2 border border-slate-300 dark:border-slate-600 rounded-md bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-200 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition"
|
||||
/>
|
||||
{inputValue && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-slate-500 hover:text-slate-800 dark:hover:text-slate-200"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 w-full md:w-auto flex-shrink-0">
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center justify-center gap-2 w-full md:w-auto px-4 py-2 bg-slate-700 text-white font-semibold rounded-md hover:bg-slate-800 dark:bg-slate-600 dark:hover:bg-slate-500 transition-colors"
|
||||
>
|
||||
<Search className="w-5 h-5" />
|
||||
<span>Search</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRefresh}
|
||||
disabled={isLoading}
|
||||
className="flex items-center justify-center gap-2 w-full md:w-auto px-4 py-2 bg-primary text-white font-semibold rounded-md hover:bg-blue-600 transition-colors disabled:bg-slate-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
<RefreshCw className={`w-5 h-5 ${isLoading ? 'animate-spin' : ''}`} />
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderBar;
|
||||
Reference in New Issue
Block a user