QuantIQ / frontend /src /components /Navbar.tsx
Karan6124's picture
feat: implement custom user profile avatar upload to Cloudinary
9fc8a82
Raw
History Blame Contribute Delete
3.74 kB
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>
);
}