File size: 3,739 Bytes
9fc8a82 74cf3f1 1755110 f061065 9fc8a82 1755110 9fc8a82 1755110 f061065 74cf3f1 1755110 9fc8a82 1755110 | 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 | import { useRef, useState } from 'react';
import { Wallet, LogOut, Loader2, Camera } from 'lucide-react';
import Logo from './Logo';
interface NavbarProps {
user: any;
onRechargeClick: () => void;
onLogout: () => void;
onLogoClick?: () => void;
onAvatarUpload?: (newUrl: string) => void;
}
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
export default function Navbar({ user, onRechargeClick, onLogout, onLogoClick, onAvatarUpload }: NavbarProps) {
const fileInputRef = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState(false);
const handleAvatarClick = () => {
if (fileInputRef.current && !uploading) {
fileInputRef.current.click();
}
};
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
alert('Please upload an image file.');
return;
}
setUploading(true);
const formData = new FormData();
formData.append('file', file);
const token = localStorage.getItem('quantiq_jwt');
try {
const response = await fetch(`${API_URL}/api/v1/users/avatar`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData,
});
if (!response.ok) {
throw new Error('Failed to upload avatar');
}
const data = await response.json();
if (data.picture_url && onAvatarUpload) {
onAvatarUpload(data.picture_url);
}
} catch (err) {
console.error('Avatar upload error:', err);
alert('Failed to upload avatar. Please try again.');
} finally {
setUploading(false);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}
};
return (
<header className="dashboard-header">
<div
className={`header-left ${onLogoClick ? 'cursor-pointer hover:opacity-85 active:scale-[0.98]' : ''} transition-all`}
onClick={onLogoClick}
>
<Logo size={32} />
<span className="logo-text">QuantIQ</span>
</div>
<div className="header-right">
{user && (
<div className="credits-badge">
<Wallet size={16} />
<span>{user.credits} Credits</span>
<button className="recharge-btn" onClick={onRechargeClick}>
RECHARGE
</button>
</div>
)}
<div className="user-profile">
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept="image/*"
style={{ display: 'none' }}
/>
<div
className="avatar-upload-wrapper"
onClick={handleAvatarClick}
title="Click to upload custom avatar"
>
{user?.pictureUrl ? (
<img src={user.pictureUrl} alt="Avatar" className="avatar" />
) : (
<Camera size={14} style={{ color: 'var(--neon-cyan)' }} />
)}
<div className="avatar-hover-overlay">
<Camera size={12} style={{ color: '#fff' }} />
</div>
{uploading && (
<div className="avatar-upload-loading">
<Loader2 size={12} className="animate-spin" style={{ color: 'var(--neon-cyan)' }} />
</div>
)}
</div>
<span className="profile-name">{user?.fullName || 'User'}</span>
</div>
<button className="logout-btn" onClick={onLogout} title="Log Out">
<LogOut size={16} />
</button>
</div>
</header>
);
}
|