import { useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { Sparkles, X } from 'lucide-react' import { useLocale } from '@meshkee/dashboard-ui' import { useToast } from '../context/ToastContext' import { useT } from '../i18n/useT' import { ApiError } from '../lib/api' import { listProductCategories, mapProductCategoryToUi } from '../services/productCategoryService' import { createProductByAi, type ProductAiLanguage } from '../services/productAiService' import type { Category } from '../types/category' import { flattenCategories } from '../utils/categories' import { SearchableSelect } from './SearchableSelect' import modalStyles from './CategoryModal.module.css' import aiStyles from '../styles/ai.module.css' import styles from './AddProductByAiModal.module.css' import fieldStyles from '../pages/AddNewProductPage.module.css' interface AddProductByAiModalProps { open: boolean onClose: () => void onCreated: (productId: string) => void } const ANIMATION_MS = 220 export function AddProductByAiModal({ open, onClose, onCreated, }: AddProductByAiModalProps) { const t = useT() const { locale } = useLocale() const { showToast } = useToast() const [mounted, setMounted] = useState(open) const [closing, setClosing] = useState(false) const [categories, setCategories] = useState([]) const [categoryId, setCategoryId] = useState('') const [prompt, setPrompt] = useState('') const [language, setLanguage] = useState(locale === 'fa' ? 'fa' : 'en') const [isLoadingCategories, setIsLoadingCategories] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState('') const categoryOptions = useMemo(() => flattenCategories(categories), [categories]) const promptDir = language === 'fa' ? 'rtl' : 'ltr' useEffect(() => { if (open) { setMounted(true) setClosing(false) setCategoryId('') setPrompt('') setLanguage(locale === 'fa' ? 'fa' : 'en') setError('') } else if (mounted) { setClosing(true) const timer = setTimeout(() => { setMounted(false) setClosing(false) }, ANIMATION_MS) return () => clearTimeout(timer) } }, [open, mounted, locale]) useEffect(() => { if (!open) return const controller = new AbortController() void loadCategories(controller.signal) return () => controller.abort() }, [open]) useEffect(() => { if (!mounted || closing) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !isSubmitting) onClose() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) }, [mounted, closing, isSubmitting, onClose]) async function loadCategories(signal?: AbortSignal) { setIsLoadingCategories(true) setError('') try { const items = await listProductCategories(signal) setCategories(items.map(mapProductCategoryToUi)) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { setError(err.message) } else { setError(t('products.ai.loadCategoriesError')) } } finally { setIsLoadingCategories(false) } } async function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!categoryId || !prompt.trim()) return setIsSubmitting(true) setError('') try { const result = await createProductByAi({ categoryId, name: prompt.trim(), language, }) showToast(result.message, 'success') onCreated(result.product.id) onClose() } catch (err) { if (err instanceof ApiError) { setError(err.message) } else if (err instanceof Error) { setError(err.message) } else { setError(t('products.ai.createError')) } } finally { setIsSubmitting(false) } } if (!mounted) return null return createPortal(
e.target === e.currentTarget && !isSubmitting && onClose()} role="presentation" >

{t('products.ai.title')}

{t('products.ai.subtitle')}

void handleSubmit(e)}>