mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
fix: add pending state to export button to prevent duplicate clicks
Use useTransition to disable the export button and show loading state in the DSL export confirm modal during async export operations.
This commit is contained in:
parent
6ea16837ff
commit
808a32c457
@ -11,7 +11,7 @@ import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLi
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState, useTransition } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
@ -79,6 +79,7 @@ const AppCard = ({ app, onRefresh, onlineUsers = [] }: AppCardProps) => {
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [showAccessControl, setShowAccessControl] = useState(false)
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
const [exporting, startExport] = useTransition()
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
try {
|
||||
@ -186,14 +187,14 @@ const AppCard = ({ app, onRefresh, onlineUsers = [] }: AppCardProps) => {
|
||||
|
||||
const exportCheck = async () => {
|
||||
if (app.mode !== AppModeEnum.WORKFLOW && app.mode !== AppModeEnum.ADVANCED_CHAT) {
|
||||
onExport()
|
||||
await onExport()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const workflowDraft = await fetchWorkflowDraft(`/apps/${app.id}/workflows/draft`)
|
||||
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
|
||||
if (list.length === 0) {
|
||||
onExport()
|
||||
await onExport()
|
||||
return
|
||||
}
|
||||
setSecretEnvList(list)
|
||||
@ -235,11 +236,13 @@ const AppCard = ({ app, onRefresh, onlineUsers = [] }: AppCardProps) => {
|
||||
e.preventDefault()
|
||||
setShowDuplicateModal(true)
|
||||
}
|
||||
const onClickExport = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const onClickExport = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
props.onClick?.()
|
||||
e.preventDefault()
|
||||
exportCheck()
|
||||
startExport(async () => {
|
||||
await exportCheck()
|
||||
})
|
||||
}
|
||||
const onClickSwitch = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
@ -288,7 +291,7 @@ const AppCard = ({ app, onRefresh, onlineUsers = [] }: AppCardProps) => {
|
||||
<button type="button" className="mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover" onClick={onClickDuplicate}>
|
||||
<span className="system-sm-regular text-text-secondary">{t('duplicate', { ns: 'app' })}</span>
|
||||
</button>
|
||||
<button type="button" className="mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover" onClick={onClickExport}>
|
||||
<button type="button" disabled={exporting || secretEnvList.length > 0} className="mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50" onClick={onClickExport}>
|
||||
<span className="system-sm-regular text-text-secondary">{t('export', { ns: 'app' })}</span>
|
||||
</button>
|
||||
{(app.mode === AppModeEnum.COMPLETION || app.mode === AppModeEnum.CHAT) && (
|
||||
|
||||
@ -3,7 +3,7 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import { RiCloseLine, RiLock2Line } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useState, useTransition } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
@ -13,7 +13,7 @@ import { cn } from '@/utils/classnames'
|
||||
|
||||
export type DSLExportConfirmModalProps = {
|
||||
envList: EnvironmentVariable[]
|
||||
onConfirm: (state: boolean) => void
|
||||
onConfirm: (state: boolean) => void | Promise<void>
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
@ -25,10 +25,13 @@ const DSLExportConfirmModal = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [exportSecrets, setExportSecrets] = useState<boolean>(false)
|
||||
const [exporting, startExport] = useTransition()
|
||||
|
||||
const submit = () => {
|
||||
onConfirm(exportSecrets)
|
||||
onClose()
|
||||
startExport(async () => {
|
||||
await onConfirm(exportSecrets)
|
||||
onClose()
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
@ -38,7 +41,7 @@ const DSLExportConfirmModal = ({
|
||||
className={cn('w-[480px] max-w-[480px]')}
|
||||
>
|
||||
<div className="title-2xl-semi-bold relative pb-6 text-text-primary">{t('env.export.title', { ns: 'workflow' })}</div>
|
||||
<div className="absolute right-4 top-4 cursor-pointer p-2" onClick={onClose}>
|
||||
<div className={cn('absolute right-4 top-4 p-2', exporting ? 'pointer-events-none opacity-50' : 'cursor-pointer')} onClick={onClose}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
<div className="relative">
|
||||
@ -77,8 +80,8 @@ const DSLExportConfirmModal = ({
|
||||
<div className="system-sm-medium cursor-pointer text-text-primary" onClick={() => setExportSecrets(!exportSecrets)}>{t('env.export.checkbox', { ns: 'workflow' })}</div>
|
||||
</div>
|
||||
<div className="flex flex-row-reverse pt-6">
|
||||
<Button className="ml-2" variant="primary" onClick={submit}>{exportSecrets ? t('env.export.export', { ns: 'workflow' }) : t('env.export.ignore', { ns: 'workflow' })}</Button>
|
||||
<Button onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
<Button className="ml-2" variant="primary" loading={exporting} disabled={exporting} onClick={submit}>{exportSecrets ? t('env.export.export', { ns: 'workflow' }) : t('env.export.ignore', { ns: 'workflow' })}</Button>
|
||||
<Button disabled={exporting} onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user