feat: enhance file upload UI for avatar and header images with improved styling and error handling
This commit is contained in:
+65
-59
@@ -491,37 +491,40 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Avatar</label>
|
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Avatar</label>
|
||||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
|
||||||
<input
|
<label className="btn btn-ghost btn-sm" style={{ cursor: 'pointer' }}>
|
||||||
type="file"
|
{isSaving ? 'Uploading...' : 'Choose File'}
|
||||||
accept="image/*"
|
<input
|
||||||
onChange={async (e) => {
|
type="file"
|
||||||
const file = e.target.files?.[0];
|
accept="image/*"
|
||||||
if (!file) return;
|
onChange={async (e) => {
|
||||||
setIsSaving(true);
|
const file = e.target.files?.[0];
|
||||||
try {
|
if (!file) return;
|
||||||
const formData = new FormData();
|
setIsSaving(true);
|
||||||
formData.append('file', file);
|
try {
|
||||||
const res = await fetch('/api/uploads', {
|
const formData = new FormData();
|
||||||
method: 'POST',
|
formData.append('file', file);
|
||||||
body: formData,
|
const res = await fetch('/api/uploads', {
|
||||||
});
|
method: 'POST',
|
||||||
const data = await res.json();
|
body: formData,
|
||||||
if (data.url) {
|
});
|
||||||
setProfileForm(prev => ({ ...prev, avatarUrl: data.url }));
|
const data = await res.json();
|
||||||
|
if (data.url) {
|
||||||
|
setProfileForm(prev => ({ ...prev, avatarUrl: data.url }));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setSaveError('Upload failed');
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}}
|
||||||
console.error(err);
|
disabled={isSaving}
|
||||||
setSaveError('Upload failed');
|
style={{ display: 'none' }}
|
||||||
} finally {
|
/>
|
||||||
setIsSaving(false);
|
</label>
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isSaving}
|
|
||||||
style={{ fontSize: '13px' }}
|
|
||||||
/>
|
|
||||||
{profileForm.avatarUrl && (
|
{profileForm.avatarUrl && (
|
||||||
<div style={{ width: '32px', height: '32px', borderRadius: '50%', overflow: 'hidden' }}>
|
<div style={{ width: '40px', height: '40px', borderRadius: '50%', overflow: 'hidden', border: '1px solid var(--border)' }}>
|
||||||
<img src={profileForm.avatarUrl} alt="Preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
<img src={profileForm.avatarUrl} alt="Preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -529,37 +532,40 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Header</label>
|
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Header</label>
|
||||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
|
||||||
<input
|
<label className="btn btn-ghost btn-sm" style={{ cursor: 'pointer' }}>
|
||||||
type="file"
|
{isSaving ? 'Uploading...' : 'Choose File'}
|
||||||
accept="image/*"
|
<input
|
||||||
onChange={async (e) => {
|
type="file"
|
||||||
const file = e.target.files?.[0];
|
accept="image/*"
|
||||||
if (!file) return;
|
onChange={async (e) => {
|
||||||
setIsSaving(true);
|
const file = e.target.files?.[0];
|
||||||
try {
|
if (!file) return;
|
||||||
const formData = new FormData();
|
setIsSaving(true);
|
||||||
formData.append('file', file);
|
try {
|
||||||
const res = await fetch('/api/uploads', {
|
const formData = new FormData();
|
||||||
method: 'POST',
|
formData.append('file', file);
|
||||||
body: formData,
|
const res = await fetch('/api/uploads', {
|
||||||
});
|
method: 'POST',
|
||||||
const data = await res.json();
|
body: formData,
|
||||||
if (data.url) {
|
});
|
||||||
setProfileForm(prev => ({ ...prev, headerUrl: data.url }));
|
const data = await res.json();
|
||||||
|
if (data.url) {
|
||||||
|
setProfileForm(prev => ({ ...prev, headerUrl: data.url }));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setSaveError('Upload failed');
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}}
|
||||||
console.error(err);
|
disabled={isSaving}
|
||||||
setSaveError('Upload failed');
|
style={{ display: 'none' }}
|
||||||
} finally {
|
/>
|
||||||
setIsSaving(false);
|
</label>
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isSaving}
|
|
||||||
style={{ fontSize: '13px' }}
|
|
||||||
/>
|
|
||||||
{profileForm.headerUrl && (
|
{profileForm.headerUrl && (
|
||||||
<div style={{ width: '48px', height: '32px', borderRadius: '4px', overflow: 'hidden' }}>
|
<div style={{ width: '80px', height: '40px', borderRadius: '4px', overflow: 'hidden', border: '1px solid var(--border)' }}>
|
||||||
<img src={profileForm.headerUrl} alt="Preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
<img src={profileForm.headerUrl} alt="Preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -568,7 +574,7 @@ export default function ProfilePage() {
|
|||||||
{saveError && (
|
{saveError && (
|
||||||
<div style={{ color: 'var(--error)', fontSize: '13px' }}>{saveError}</div>
|
<div style={{ color: 'var(--error)', fontSize: '13px' }}>{saveError}</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', marginTop: '8px' }}>
|
||||||
<button className="btn btn-ghost" onClick={() => setIsEditing(false)} disabled={isSaving}>
|
<button className="btn btn-ghost" onClick={() => setIsEditing(false)} disabled={isSaving}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user