dify/web/app/components/apps/import-from-marketplace-template-modal.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

174 lines
6.4 KiB
TypeScript

'use client'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import { MARKETPLACE_API_PREFIX } from '@/config'
import {
fetchMarketplaceTemplateDSL,
useMarketplaceTemplateDetail,
} from '@/service/marketplace-templates'
type ImportFromMarketplaceTemplateModalProps = {
templateId: string
onClose: () => void
onConfirm: (dslContent: string) => void
}
const ImportFromMarketplaceTemplateModal = ({
templateId,
onClose,
onConfirm,
}: ImportFromMarketplaceTemplateModalProps) => {
const { t } = useTranslation()
const { data, isLoading, isError } = useMarketplaceTemplateDetail(templateId)
const template = data?.data
const [importing, setImporting] = useState(false)
const isImportingRef = useRef(false)
const CATEGORY_I18N_MAP: Record<string, string> = useMemo(
() => ({
marketing: t(($) => $['marketplace.template.category.marketing'], { ns: 'app' }),
sales: t(($) => $['marketplace.template.category.sales'], { ns: 'app' }),
support: t(($) => $['marketplace.template.category.support'], { ns: 'app' }),
operations: t(($) => $['marketplace.template.category.operations'], { ns: 'app' }),
it: t(($) => $['marketplace.template.category.it'], { ns: 'app' }),
knowledge: t(($) => $['marketplace.template.category.knowledge'], { ns: 'app' }),
design: t(($) => $['marketplace.template.category.design'], { ns: 'app' }),
}),
[t],
)
const translateCategory = useCallback(
(slug: string) => {
return CATEGORY_I18N_MAP[slug] ?? slug
},
[CATEGORY_I18N_MAP],
)
const handleConfirm = useCallback(async () => {
if (isImportingRef.current) return
isImportingRef.current = true
setImporting(true)
try {
const dsl = await fetchMarketplaceTemplateDSL(templateId)
onConfirm(dsl)
} catch {
toast.error(t(($) => $['marketplace.template.importFailed'], { ns: 'app' }))
} finally {
setImporting(false)
isImportingRef.current = false
}
}, [templateId, onConfirm, t])
return (
<Dialog
open
onOpenChange={(open) => {
if (!open) onClose()
}}
>
<DialogContent className="w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl">
<div className="flex items-center justify-between pt-6 pr-5 pb-3 pl-6 title-2xl-semi-bold text-text-primary">
{t(($) => $['marketplace.template.modalTitle'], { ns: 'app' })}
<div className="flex size-8 cursor-pointer items-center" onClick={onClose}>
<RiCloseLine className="size-5 text-text-tertiary" />
</div>
</div>
<div className="px-6 py-4">
{isLoading && (
<div className="flex items-center justify-center py-8">
<div className="system-md-regular text-text-tertiary">Loading...</div>
</div>
)}
{isError && (
<div className="flex items-center justify-center py-8">
<div className="system-md-regular text-text-destructive">
{t(($) => $['marketplace.template.fetchFailed'], { ns: 'app' })}
</div>
</div>
)}
{template && (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<AppIcon
size="large"
iconType={template.icon_file_key ? 'image' : 'emoji'}
icon={template.icon || 'page_facing_up'}
background={template.icon_file_key ? undefined : template.icon_background}
imageUrl={
template.icon_file_key
? `${MARKETPLACE_API_PREFIX}/templates/${template.id}/icon`
: undefined
}
/>
<div className="flex flex-col">
<div className="system-md-semibold text-text-primary">
{template.template_name}
</div>
<div className="flex items-center gap-1 system-xs-regular text-text-tertiary">
<span>
{t(($) => $['marketplace.template.publishedBy'], { ns: 'app' })}{' '}
{template.publisher_unique_handle}
</span>
<span>·</span>
<span>
{t(($) => $['marketplace.template.usageCount'], { ns: 'app' })}{' '}
{template.usage_count}
</span>
</div>
</div>
</div>
{template.overview && (
<div className="flex flex-col gap-1">
<div className="system-xs-medium-uppercase text-text-tertiary">
{t(($) => $['marketplace.template.overview'], { ns: 'app' })}
</div>
<div className="system-sm-regular text-text-secondary">{template.overview}</div>
</div>
)}
{template.categories.length > 0 && (
<div className="flex flex-wrap items-center gap-2">
{template.categories.map((cat) => (
<span
key={cat}
className="inline-flex items-center rounded-full bg-components-label-gray px-2.5 py-1 system-sm-regular text-text-secondary"
>
{translateCategory(cat)}
</span>
))}
</div>
)}
</div>
)}
</div>
<div className="flex justify-end px-6 py-5">
<Button className="mr-2" onClick={onClose}>
{t(($) => $['newApp.Cancel'], { ns: 'app' })}
</Button>
<Button
variant="primary"
disabled={isLoading || isError || importing}
loading={importing}
onClick={handleConfirm}
>
{t(($) => $['marketplace.template.importConfirm'], { ns: 'app' })}
</Button>
</div>
</DialogContent>
</Dialog>
)
}
export default ImportFromMarketplaceTemplateModal