admin/app/(dashboard)/blog-articles/_components/ArticleFeaturedImageUploader.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

99 lines
2.8 KiB
TypeScript

'use client'
import { useState } from 'react'
import Image from 'next/image'
import { addToast } from '@/lib/toast'
import Button from '@/components/formElements/Button'
import TrashIcon from '@/components/icons/TrashIcon'
import ImageUploadIcon from '@/components/icons/ImageUploadIcon'
import axiosInstance from '@/config/axios'
import { parseUploadedFile } from '@/helpers'
import { prepareImageForUpload, SAFE_IMAGE_ACCEPT } from '@/lib/fileValidation'
interface ArticleFeaturedImageUploaderProps {
value: string
onChange: (url: string) => void
}
const ArticleFeaturedImageUploader = ({ value, onChange }: ArticleFeaturedImageUploaderProps) => {
const [uploading, setUploading] = useState(false)
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
e.target.value = ''
if (!file) return
const prepared = await prepareImageForUpload(file)
if (typeof prepared === 'string') {
addToast({ title: prepared, color: 'danger' })
return
}
setUploading(true)
try {
const data = new FormData()
data.append('file', prepared)
const result = await axiosInstance.post('/uploads', data)
const uploaded = parseUploadedFile(result.data)
if (!uploaded) throw new Error('آپلود ناموفق بود')
onChange(uploaded.url)
} catch {
addToast({ title: 'آپلود تصویر ناموفق بود', color: 'danger' })
} finally {
setUploading(false)
}
}
if (value) {
return (
<div className="relative w-full max-w-xs overflow-hidden rounded-2xl border border-secondary-40">
<Image
unoptimized
alt=""
className="h-40 w-full object-cover"
height={160}
src={value}
width={320}
/>
<Button
iconOnly
aria-label="حذف تصویر شاخص"
className="absolute top-2 end-2 rounded-full bg-white/90"
size="sm"
variant="light"
onClick={() => {
onChange('')
}}
>
<TrashIcon className="size-4 text-fourth-900" />
</Button>
</div>
)
}
return (
<label className="flex h-32 w-full max-w-xs cursor-pointer flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-primary-200 bg-primary-50">
<input
accept={SAFE_IMAGE_ACCEPT}
className="hidden"
disabled={uploading}
type="file"
onChange={handleFileChange}
/>
<ImageUploadIcon className="size-8 text-primary" />
<span className="text-xs text-primary">{uploading ? 'در حال آپلود...' : 'افزودن تصویر شاخص'}</span>
</label>
)
}
export default ArticleFeaturedImageUploader