Fix URL validation blocking uploaded photos in AddCardModal
This commit is contained in:
1 parent
0c98b1babf
commit
15fbe1db88
1 file changed
+106
-8
@@ -89,7 +89,7 @@ export const AddCardModal: React.FC<AddCardModalProps> = ({ isOpen, onClose, onA
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 text-xs">
|
<form onSubmit={handleSubmit} noValidate className="space-y-4 text-xs">
|
||||||
{/* Card Type */}
|
{/* Card Type */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-neutral-400 mb-1">Card Type</label>
|
<label className="block text-neutral-400 mb-1">Card Type</label>
|
||||||
@@ -159,15 +159,30 @@ export const AddCardModal: React.FC<AddCardModalProps> = ({ isOpen, onClose, onA
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="text"
|
||||||
value={imageUrl}
|
value={imageUrl}
|
||||||
onChange={(e) => setImageUrl(e.target.value)}
|
onChange={(e) => setImageUrl(e.target.value)}
|
||||||
placeholder="Or paste an image URL (https://...)"
|
placeholder="Or paste an image URL (https://... or /uploads/...)"
|
||||||
className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500 text-[11px]"
|
className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500 text-[11px]"
|
||||||
/>
|
/>
|
||||||
{imageUrl && (
|
{imageUrl && (
|
||||||
<div className="w-16 h-16 rounded border border-neutral-700 overflow-hidden relative mt-1">
|
<div className="flex items-center gap-2.5 mt-1 bg-neutral-900/80 p-2 rounded-lg border border-neutral-800">
|
||||||
<img src={imageUrl} alt="Preview" className="w-full h-full object-cover" />
|
<div className="w-12 h-12 rounded border border-neutral-700 overflow-hidden relative shrink-0">
|
||||||
|
<img src={imageUrl} alt="Preview" className="w-full h-full object-cover" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-[11px] overflow-hidden min-w-0">
|
||||||
|
<span className="text-emerald-400 font-medium flex items-center gap-1">
|
||||||
|
<span>✓ Photo Ready</span>
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-neutral-400 font-mono text-[10px]">{imageUrl}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setImageUrl('')}
|
||||||
|
className="text-neutral-400 hover:text-red-400 text-left underline text-[10px] mt-0.5"
|
||||||
|
>
|
||||||
|
Clear photo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -246,13 +261,50 @@ export const EditCardModal: React.FC<EditCardModalProps> = ({
|
|||||||
const [title, setTitle] = useState(card.title || '');
|
const [title, setTitle] = useState(card.title || '');
|
||||||
const [content, setContent] = useState(card.content || '');
|
const [content, setContent] = useState(card.content || '');
|
||||||
const [color, setColor] = useState(card.color || '#FFF9A6');
|
const [color, setColor] = useState(card.color || '#FFF9A6');
|
||||||
|
const [imageUrl, setImageUrl] = useState(card.imageUrl || '');
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
|
const handleEditFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (ev) => {
|
||||||
|
if (typeof ev.target?.result === 'string') {
|
||||||
|
setImageUrl(ev.target.result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
setIsUploading(true);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
fetch('/api/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
setIsUploading(false);
|
||||||
|
if (data.success && data.url) {
|
||||||
|
setImageUrl(data.url);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn('Upload error:', err);
|
||||||
|
setIsUploading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
onSave({
|
onSave({
|
||||||
...card,
|
...card,
|
||||||
title: title.trim() || undefined,
|
title: title.trim() || undefined,
|
||||||
content: content.trim(),
|
content: content.trim(),
|
||||||
color
|
color,
|
||||||
|
imageUrl: card.type === 'polaroid' ? (imageUrl.trim() || undefined) : card.imageUrl,
|
||||||
|
photoCaption: card.type === 'polaroid' ? (title.trim() || content.trim()) : undefined
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
@@ -281,15 +333,61 @@ export const EditCardModal: React.FC<EditCardModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-neutral-400 mb-1">Content</label>
|
<label className="block text-neutral-400 mb-1">
|
||||||
|
{card.type === 'polaroid' ? 'Back / Note Text' : 'Content'}
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
rows={4}
|
rows={3}
|
||||||
value={content}
|
value={content}
|
||||||
onChange={(e) => setContent(e.target.value)}
|
onChange={(e) => setContent(e.target.value)}
|
||||||
className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500"
|
className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Photo management for Polaroid */}
|
||||||
|
{card.type === 'polaroid' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center text-neutral-400">
|
||||||
|
<span>Photo / Snapshot</span>
|
||||||
|
{isUploading && (
|
||||||
|
<span className="text-[10px] text-amber-400 font-mono animate-pulse">
|
||||||
|
Uploading...
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<label className="flex-1 flex items-center justify-center gap-1.5 py-1.5 px-3 bg-neutral-800 border border-neutral-700 hover:border-amber-500 rounded cursor-pointer text-amber-300 font-medium transition text-center">
|
||||||
|
<span>📸 {isUploading ? 'Uploading...' : 'Replace Photo'}</span>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleEditFileUpload}
|
||||||
|
className="hidden"
|
||||||
|
disabled={isUploading}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={imageUrl}
|
||||||
|
onChange={(e) => setImageUrl(e.target.value)}
|
||||||
|
placeholder="Or paste an image URL (https://... or /uploads/...)"
|
||||||
|
className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500 text-[11px]"
|
||||||
|
/>
|
||||||
|
{imageUrl && (
|
||||||
|
<div className="flex items-center gap-2.5 mt-1 bg-neutral-900/80 p-2 rounded-lg border border-neutral-800">
|
||||||
|
<div className="w-12 h-12 rounded border border-neutral-700 overflow-hidden relative shrink-0">
|
||||||
|
<img src={imageUrl} alt="Preview" className="w-full h-full object-cover" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-[11px] overflow-hidden min-w-0">
|
||||||
|
<span className="text-emerald-400 font-medium">✓ Photo Attached</span>
|
||||||
|
<span className="truncate text-neutral-400 font-mono text-[10px]">{imageUrl}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-neutral-400 mb-1">Paper Tint</label>
|
<label className="block text-neutral-400 mb-1">Paper Tint</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|||||||
Reference in new issue
Block a user