import { useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { Link2, X } from 'lucide-react' import { useLocale } from '@meshkee/dashboard-ui' import { useT } from '../i18n/useT' import type { BusinessMessageKey } from '../i18n/messages' import { ApiError } from '../lib/api' import { listBlogCategories } from '../services/blogCategoryService' import { listPortfolioCategories } from '../services/portfolioCategoryService' import { listProductCategories, mapProductCategoryToUi } from '../services/productCategoryService' import { listVideoCategories } from '../services/videoCategoryService' import { listWorkshopCategories } from '../services/workshopCategoryService' import type { ProductCategory } from '../types/category' import { WEBSITE_LINK_KINDS, buildWebsiteCategoryUrl, buildWebsiteTagsUrl, type WebsiteLinkFilter, type WebsiteLinkKind, } from '../utils/websiteLinks' import { CategoryTreeSelect } from './CategoryTreeSelect' import { TagInput } from './TagInput' import modalStyles from './CategoryModal.module.css' import styles from './GenerateWebsiteLinkModal.module.css' export type { WebsiteLinkKind } export { buildWebsiteCategoryUrl, buildWebsiteTagsUrl } const KIND_LABEL: Record = { product: 'website.linkGenerator.types.product', storeItem: 'website.linkGenerator.types.storeItem', blog: 'website.linkGenerator.types.blog', portfolio: 'website.linkGenerator.types.portfolio', video: 'website.linkGenerator.types.video', workshop: 'website.linkGenerator.types.workshop', } const ANIMATION_MS = 220 interface GenerateWebsiteLinkModalProps { open: boolean onClose: () => void onPick: (url: string) => void } interface GenerateWebsiteLinkButtonProps { onPick: (url: string) => void disabled?: boolean } async function loadCategories(kind: WebsiteLinkKind, signal: AbortSignal) { switch (kind) { case 'blog': return listBlogCategories(signal) case 'portfolio': return listPortfolioCategories(signal) case 'video': return listVideoCategories(signal) case 'workshop': return listWorkshopCategories(signal) case 'product': case 'storeItem': return listProductCategories(signal) } } export function GenerateWebsiteLinkButton({ onPick, disabled = false }: GenerateWebsiteLinkButtonProps) { const t = useT() const [open, setOpen] = useState(false) return ( <> setOpen(false)} onPick={(url) => { onPick(url) setOpen(false) }} /> ) } export function GenerateWebsiteLinkModal({ open, onClose, onPick }: GenerateWebsiteLinkModalProps) { const t = useT() const { locale } = useLocale() const isFa = locale === 'fa' const [mounted, setMounted] = useState(open) const [closing, setClosing] = useState(false) const [kind, setKind] = useState('product') const [filterBy, setFilterBy] = useState('category') const [categories, setCategories] = useState([]) const [categoryId, setCategoryId] = useState('') const [tags, setTags] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState('') useEffect(() => { if (open) { setMounted(true) setClosing(false) setKind('product') setFilterBy('category') setCategories([]) setCategoryId('') setTags([]) setError('') } else if (mounted) { setClosing(true) const timer = setTimeout(() => { setMounted(false) setClosing(false) }, ANIMATION_MS) return () => clearTimeout(timer) } }, [open, mounted]) useEffect(() => { if (!mounted || closing) return const onKey = (e: KeyboardEvent) => { if (e.key !== 'Escape') return e.preventDefault() e.stopImmediatePropagation() onClose() } document.addEventListener('keydown', onKey, true) return () => document.removeEventListener('keydown', onKey, true) }, [mounted, closing, onClose]) useEffect(() => { if (!open || filterBy !== 'category') return const controller = new AbortController() setLoading(true) setError('') setCategoryId('') setCategories([]) void loadCategories(kind, controller.signal) .then((items) => { setCategories(items) }) .catch((err) => { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { setError(err.message) } else { setError(t('website.linkGenerator.errorLoad')) } }) .finally(() => { if (!controller.signal.aborted) setLoading(false) }) return () => controller.abort() }, [open, kind, filterBy, t]) const uiCategories = useMemo( () => categories.map(mapProductCategoryToUi), [categories], ) const selected = categories.find((category) => category.id === categoryId) const preview = filterBy === 'category' ? selected ? buildWebsiteCategoryUrl(kind, selected) : '' : buildWebsiteTagsUrl(kind, tags) if (!mounted) return null function handleSubmit(e: React.FormEvent) { e.preventDefault() e.stopPropagation() if (!preview) return onPick(preview) } function handleFilterChange(next: WebsiteLinkFilter) { setFilterBy(next) setError('') if (next === 'tag') { setCategoryId('') } else { setTags([]) } } return createPortal(
e.target === e.currentTarget && onClose()} role="presentation" >

{t('website.linkGenerator.subtitle')}

e.stopPropagation()} >
{filterBy === 'category' ? (
{loading ? (

{t('website.linkGenerator.loading')}

) : uiCategories.length === 0 ? (

{t('website.linkGenerator.empty')}

) : ( )}
) : (

{t('website.linkGenerator.tagsHint')}

)} {preview ? (

{preview}

) : null} {error ?

{error}

: null}
, document.body, ) }