fix(admin): resolve 500 on missing product update and add direct image URL & paste support
Some checks failed
Deploy Canina / deploy (push) Has been cancelled

This commit is contained in:
parsa aghaei 2026-07-29 16:42:23 +03:30
parent a3291cc468
commit 9c8286c63f
3 changed files with 63 additions and 14 deletions

View File

@ -168,6 +168,11 @@ export class AdminService {
}
async updateProduct(id: string, data: any) {
const existing = await this.prisma.product.findUnique({ where: { id } });
if (!existing) {
throw new NotFoundException(`محصولی با شناسه ${id} یافت نشد.`);
}
const product = await this.prisma.product.update({
where: { id },
data: {

View File

@ -36,6 +36,17 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
if (!isOpen) return;
const handlePaste = (e: ClipboardEvent) => {
const clipboardText = e.clipboardData?.getData('text');
if (clipboardText && (clipboardText.startsWith('http://') || clipboardText.startsWith('https://'))) {
e.preventDefault();
setPasteStatus('لینک تصویر چسبانده شد!');
onSelect(clipboardText);
if (!multiple) onClose();
if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
pasteTimerRef.current = setTimeout(() => setPasteStatus(null), 3000);
return;
}
const items = e.clipboardData?.items;
if (!items) return;

View File

@ -692,40 +692,73 @@ export default function Products() {
</div>
{/* Media Tab */}
<div className={activeTab === 'media' ? 'block space-y-4' : 'hidden'}>
<div className={activeTab === 'media' ? 'block space-y-6' : 'hidden'}>
{/* Direct Image URL Input Box */}
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm space-y-3">
<label className="text-sm font-bold text-gray-700 flex items-center justify-between">
<span className="flex items-center gap-2">
<ImageIcon className="w-4 h-4 text-purple-600" />
لینک مستقیم آدرس تصویر (External / Direct URL)
</span>
<span className="text-[11px] text-gray-400 font-normal">امکان چسباندن (Ctrl+V) مستقیم آدرس اینترنتی تصویر</span>
</label>
<div className="flex gap-2">
<input
type="text"
value={formData.imageUrl}
onChange={(e) => {
setFormData({...formData, imageUrl: e.target.value});
setMediaImageError(false);
}}
placeholder="https://example.com/images/product.png یا /uploads/photo.jpg"
className="flex-1 px-4 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-sm"
dir="ltr"
/>
<button
type="button"
onClick={() => setIsMediaSelectorOpen(true)}
className="bg-purple-100 text-purple-700 hover:bg-purple-200 px-4 py-2.5 rounded-xl text-xs font-bold transition-colors whitespace-nowrap"
>
گالری سرور
</button>
</div>
</div>
{/* Preview Container */}
<div className="flex flex-col items-center justify-center p-8 border-2 border-dashed border-gray-200 rounded-2xl bg-white">
{formData.imageUrl && !mediaImageError ? (
<div className="w-full flex flex-col items-center">
<div className="relative group">
<img
src={getProductImageUrl(formData.imageUrl)}
alt=""
alt="پیش‌نمایش تصویر محصول"
onError={() => setMediaImageError(true)}
className="max-w-xs h-48 object-contain rounded-xl shadow-md border border-gray-100"
/>
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded-xl flex items-center justify-center gap-3">
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="bg-white text-gray-800 px-4 py-2 rounded-lg font-bold text-sm">تغییر عکس</button>
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="bg-white text-gray-800 px-4 py-2 rounded-lg font-bold text-sm">انتخاب از گالری</button>
<button type="button" onClick={() => setFormData({...formData, imageUrl: ''})} className="bg-red-500 text-white px-4 py-2 rounded-lg font-bold text-sm hover:bg-red-600">حذف عکس</button>
</div>
</div>
<div className="mt-4 text-center" dir="ltr">
<p className="text-xs text-gray-500 font-mono bg-gray-100 px-4 py-2 rounded-lg inline-block">
{formData.imageUrl.split('/').pop()}
<p className="text-xs text-gray-500 font-mono bg-gray-100 px-4 py-2 rounded-lg inline-block max-w-md truncate">
{formData.imageUrl}
</p>
</div>
<p className="text-[11px] text-gray-400 mt-2">برای تغییر عکس، روی تصویر کلیک کنید یا دکمه زیر را بزنید</p>
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="mt-3 bg-purple-100 text-purple-700 hover:bg-purple-200 px-6 py-2 rounded-xl font-bold transition-colors text-sm">
انتخاب از گالری رسانه
</button>
<p className="text-[11px] text-gray-400 mt-2">تصویر با موفقیت بارگذاری و تایید شد.</p>
</div>
) : (
<>
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center text-gray-400 mb-4">
<ImageIcon className="w-8 h-8" />
<ImageIcon className="w-8 h-8 text-purple-400" />
</div>
<p className="text-gray-500 font-medium mb-4">تصویر محصول یافت نشد یا انتخاب نشده است</p>
<p className="text-xs text-gray-400 mb-4">از گالری رسانه تصویر انتخاب کنید یا با Ctrl+V از کلیپبورد بچسبانید</p>
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="bg-purple-100 text-purple-700 hover:bg-purple-200 px-6 py-2 rounded-xl font-bold transition-colors">
انتخاب از گالری رسانه
<p className="text-gray-600 font-bold mb-2">آدرس یا فایلی برای تصویر انتخاب نشده است</p>
<p className="text-xs text-gray-400 mb-4 text-center leading-relaxed">
میتوانید لینک اینترنتی تصویر را در کادر بالا وارد کنید، عکس جدید آپلود نمایید<br />
یا از کلیدهای <kbd className="px-1.5 py-0.5 bg-gray-100 rounded text-gray-700 font-mono">Ctrl+V</kbd> برای چسباندن مستقیم فایل یا لینک استفاده کنید.
</p>
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="bg-purple-600 text-white hover:bg-purple-700 px-6 py-2.5 rounded-xl font-bold transition-colors shadow-sm text-sm">
انتخاب یا آپلود در گالری
</button>
</>
)}