import { useEffect, useMemo, useState } from 'react' import { Link, useParams, useNavigate } from 'react-router-dom' import { Sparkles } from 'lucide-react' import { SearchableSelect } from '../components/SearchableSelect' import { ImageCropper } from '../components/ImageCropper' import { ImageUploader } from '../components/ImageUploader' import { TagInput } from '../components/TagInput' import { RichTextEditor } from '../components/RichTextEditor' import { Breadcrumbs } from '../components/Breadcrumbs' import { ProductFieldAiPromptModal } from '../components/ProductFieldAiPromptModal' import { Tooltip } from '../components/Tooltip' import { useToast } from '../context/ToastContext' import { ApiError } from '../lib/api' import { listProductCategories, mapProductCategoryToUi } from '../services/productCategoryService' import { listAllBrands, mapBrandToSelectOption } from '../services/brandService' import { resolveDataUrlToMediaId, resolveDataUrlsToMediaIds, } from '../services/mediaService' import { createProduct, getProduct, mapProductApiToFormState, updateProduct, } from '../services/productService' import { fillProductFieldByAi, type ProductAiFillField, } from '../services/productAiService' import type { Category, FlatCategory } from '../types/category' import { flattenCategories } from '../utils/categories' import pageStyles from '../components/PageContent.module.css' import aiStyles from '../styles/ai.module.css' import styles from './AddNewProductPage.module.css' import { useT } from '../i18n/useT' export function AddNewProductPage() { const { id } = useParams() const navigate = useNavigate() const t = useT() const { showToast } = useToast() const isEdit = Boolean(id) const [categories, setCategories] = useState([]) const [brandOptions, setBrandOptions] = useState([]) const [categoryId, setCategoryId] = useState('') const [brandId, setBrandId] = useState('') const [nameFa, setNameFa] = useState('') const [nameEn, setNameEn] = useState('') const [summary, setSummary] = useState('') const [description, setDescription] = useState('') const [thumbnail, setThumbnail] = useState(null) const [images, setImages] = useState([]) const [tags, setTags] = useState([]) const [thumbnailMediaId, setThumbnailMediaId] = useState(null) const [galleryMediaIds, setGalleryMediaIds] = useState([]) const [loaded, setLoaded] = useState(!isEdit) const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState('') const [aiField, setAiField] = useState(null) const [isAiRunning, setIsAiRunning] = useState(false) const categoryOptions = flattenCategories(categories) const selectedCategoryName = useMemo(() => { const match = categoryOptions.find((item) => item.id === categoryId) if (!match) return '' return match.nameFa?.trim() || match.nameEn || '' }, [categoryOptions, categoryId]) const canUseAiFill = Boolean(nameFa.trim() || nameEn.trim()) && !isSubmitting function openAiFill(field: ProductAiFillField) { if (!nameFa.trim() && !nameEn.trim()) { setError(t('products.form.aiFillNeedName')) return } setError('') setAiField(field) } async function runAiFill(prompt: string) { if (!aiField) return setIsAiRunning(true) try { const result = await fillProductFieldByAi({ field: aiField, prompt, title: nameEn.trim() || undefined, nameFa: nameFa.trim() || undefined, categoryId: categoryId || undefined, }) if (result.field === 'summary') { setSummary(result.text) showToast(t('products.form.aiSummaryFilled'), 'success') } else { setDescription(result.html) showToast(t('products.form.aiDescriptionFilled'), 'success') } setAiField(null) } finally { setIsAiRunning(false) } } useEffect(() => { const controller = new AbortController() async function loadFormOptions() { try { const [categoryItems, brandItems] = await Promise.all([ listProductCategories(controller.signal), listAllBrands(controller.signal), ]) setCategories(categoryItems.map(mapProductCategoryToUi)) setBrandOptions(brandItems.map(mapBrandToSelectOption)) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { setError(err.message) } else { setError(t('products.form.errorOptions')) } } } void loadFormOptions() return () => controller.abort() }, [t]) useEffect(() => { if (!isEdit || !id) return const controller = new AbortController() async function loadProduct() { setError('') try { const product = await getProduct(id!, controller.signal) const form = mapProductApiToFormState(product) setCategoryId(form.categoryId) setBrandId(form.brandId) setNameFa(form.nameFa) setNameEn(form.nameEn) setSummary(form.summary) setDescription(form.description) setThumbnail(form.thumbnail) setImages(form.images) setTags(form.tags) setThumbnailMediaId(form.thumbnailMediaId) setGalleryMediaIds(form.galleryMediaIds) setLoaded(true) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return navigate('/products/list', { replace: true }) } } void loadProduct() return () => controller.abort() }, [isEdit, id, navigate]) if (!loaded) return null async function handleSubmit(e: React.FormEvent) { e.preventDefault() setIsSubmitting(true) setError('') try { const featuredMediaId = await resolveDataUrlToMediaId( thumbnail, 'product-thumbnail.jpg', thumbnailMediaId, ) const nextGalleryMediaIds = await resolveDataUrlsToMediaIds( images, galleryMediaIds, ) const payload = { title: nameEn.trim(), nameFa: nameFa.trim(), summary: summary.trim(), descriptionHtml: description, categoryId: categoryId || undefined, brandId: brandId || undefined, featuredMediaId: featuredMediaId ?? undefined, galleryMediaIds: nextGalleryMediaIds, tags, status: 'published' as const, } if (isEdit && id) { await updateProduct(id, { ...payload, categoryId: categoryId || null, brandId: brandId || null, featuredMediaId, }) } else { await createProduct(payload) } navigate('/products/list') } catch (err) { if (err instanceof ApiError) { setError(err.message) } else if (err instanceof Error) { setError(err.message) } else { setError(t('products.form.errorSave')) } } finally { setIsSubmitting(false) } } return (

{isEdit ? t('title.editProduct') : t('products.card.new.title')}

{isEdit ? t('products.form.edit.subtitle') : t('products.card.new.desc')}

{error && (
{error}
)}
setNameFa(e.target.value)} required disabled={isSubmitting} />
setNameEn(e.target.value)} required disabled={isSubmitting} />