mirror of
https://github.com/langgenius/dify.git
synced 2026-04-16 02:16:57 +08:00
refactor(web): migrate confirm dialogs to base/ui/alert-dialog (#35127)
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
parent
381c518b23
commit
2c58b424a1
@ -207,20 +207,6 @@ vi.mock('@/app/components/app/switch-app-modal', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onConfirm, onCancel, title }: Record<string, unknown>) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="confirm-delete-modal">
|
||||
<span>{title as string}</span>
|
||||
<button data-testid="confirm-delete" onClick={onConfirm as () => void}>Delete</button>
|
||||
<button data-testid="cancel-delete" onClick={onCancel as () => void}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/dsl-export-confirm-modal', () => ({
|
||||
default: ({ onConfirm, onClose }: Record<string, unknown>) => (
|
||||
<div data-testid="dsl-export-confirm-modal">
|
||||
@ -342,14 +328,16 @@ describe('App Card Operations Flow', () => {
|
||||
fireEvent.click(deleteBtn)
|
||||
})
|
||||
|
||||
const confirmBtn = screen.queryByTestId('confirm-delete')
|
||||
if (confirmBtn) {
|
||||
fireEvent.click(confirmBtn)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('app.deleteAppConfirmTitle')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteAppMutation).toHaveBeenCalledWith('app-to-delete')
|
||||
})
|
||||
}
|
||||
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Deletable App' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteAppMutation).toHaveBeenCalledWith('app-to-delete')
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -133,26 +133,6 @@ vi.mock('@/app/components/base/drawer', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ title, isShow, onConfirm, onCancel }: {
|
||||
title: string
|
||||
content: string
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}) => (
|
||||
isShow
|
||||
? (
|
||||
<div data-testid="confirm-dialog">
|
||||
<span>{title}</span>
|
||||
<button data-testid="confirm-ok" onClick={onConfirm}>Confirm</button>
|
||||
<button data-testid="confirm-cancel" onClick={onCancel}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
default: { notify: vi.fn() },
|
||||
toast: {
|
||||
@ -242,6 +222,8 @@ vi.mock('@/app/components/tools/provider/tool-item', () => ({
|
||||
|
||||
const { default: ProviderDetail } = await import('@/app/components/tools/provider/detail')
|
||||
|
||||
const getDeleteConfirmButton = () => screen.getByRole('button', { name: /operation\.confirm$/ })
|
||||
|
||||
const makeCollection = (overrides: Partial<Collection> = {}): Collection => ({
|
||||
id: 'test-collection',
|
||||
name: 'test_collection',
|
||||
@ -465,11 +447,10 @@ describe('Tool Provider Detail Flow Integration', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('custom-modal-remove'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('Delete Tool')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
await waitFor(() => {
|
||||
expect(mockRemoveCustomCollection).toHaveBeenCalledWith('test_collection')
|
||||
expect(mockOnRefreshData).toHaveBeenCalled()
|
||||
@ -527,10 +508,10 @@ describe('Tool Provider Detail Flow Integration', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('wf-modal-remove'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('Delete Tool')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteWorkflowTool).toHaveBeenCalledWith('test-collection')
|
||||
expect(mockOnRefreshData).toHaveBeenCalled()
|
||||
|
||||
@ -5,7 +5,6 @@ import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||
@ -14,6 +13,15 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps'
|
||||
import { docURL } from './config'
|
||||
@ -679,14 +687,24 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
: (
|
||||
<Confirm
|
||||
isShow
|
||||
type="warning"
|
||||
title={t(`${I18N_PREFIX}.removeConfirmTitle`, { ns: 'app', key: t(`tracing.${type}.title`, { ns: 'app' }) })!}
|
||||
content={t(`${I18N_PREFIX}.removeConfirmContent`, { ns: 'app' })}
|
||||
onConfirm={handleRemove}
|
||||
onCancel={hideRemoveConfirm}
|
||||
/>
|
||||
<AlertDialog open onOpenChange={open => !open && hideRemoveConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t(`${I18N_PREFIX}.removeConfirmTitle`, { ns: 'app', key: t(`tracing.${type}.title`, { ns: 'app' }) })!}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t(`${I18N_PREFIX}.removeConfirmContent`, { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleRemove}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { App, AppSSO } from '@/types/app'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@ -36,24 +36,6 @@ vi.mock('@/app/components/app/duplicate-modal', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, title, onConfirm, onCancel }: {
|
||||
isShow: boolean
|
||||
title: string
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}) => (
|
||||
isShow
|
||||
? (
|
||||
<div data-testid="confirm-modal" data-title={title}>
|
||||
<button type="button" onClick={onConfirm}>Confirm</button>
|
||||
<button type="button" onClick={onCancel}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/update-dsl-modal', () => ({
|
||||
default: ({ onCancel, onBackup }: { onCancel: () => void, onBackup: () => void }) => (
|
||||
<div data-testid="import-dsl-modal">
|
||||
@ -113,7 +95,7 @@ describe('AppInfoModals', () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal={null} />)
|
||||
})
|
||||
expect(screen.queryByTestId('switch-modal')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('app.deleteAppConfirmTitle')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render SwitchAppModal when activeModal is switch', async () => {
|
||||
@ -143,14 +125,13 @@ describe('AppInfoModals', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should render Confirm for delete when activeModal is delete', async () => {
|
||||
it('should render delete alert dialog when activeModal is delete', async () => {
|
||||
await act(async () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||
})
|
||||
await waitFor(() => {
|
||||
const confirm = screen.getByTestId('confirm-modal')
|
||||
expect(confirm).toBeInTheDocument()
|
||||
expect(confirm).toHaveAttribute('data-title', 'app.deleteAppConfirmTitle')
|
||||
expect(screen.getByText('app.deleteAppConfirmTitle')).toBeInTheDocument()
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -163,14 +144,12 @@ describe('AppInfoModals', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should render export warning Confirm when activeModal is exportWarning', async () => {
|
||||
it('should render export warning alert dialog when activeModal is exportWarning', async () => {
|
||||
await act(async () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="exportWarning" />)
|
||||
})
|
||||
await waitFor(() => {
|
||||
const confirm = screen.getByTestId('confirm-modal')
|
||||
expect(confirm).toBeInTheDocument()
|
||||
expect(confirm).toHaveAttribute('data-title', 'workflow.sidebar.exportWarning')
|
||||
expect(screen.getByText('workflow.sidebar.exportWarning')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -202,20 +181,47 @@ describe('AppInfoModals', () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Cancel')).toBeInTheDocument())
|
||||
await user.click(screen.getByText('Cancel'))
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeInTheDocument())
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(defaultProps.closeModal).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should clear the delete confirmation input when delete modal is cancelled', async () => {
|
||||
const user = userEvent.setup()
|
||||
await act(async () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||
})
|
||||
|
||||
const input = await screen.findByRole('textbox')
|
||||
await user.type(input, 'wrong-name')
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(defaultProps.closeModal).toHaveBeenCalledTimes(1)
|
||||
expect(input).toHaveValue('')
|
||||
})
|
||||
|
||||
it('should not confirm delete when the form is submitted with unmatched input', async () => {
|
||||
await act(async () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||
})
|
||||
|
||||
const form = document.querySelector('form')
|
||||
expect(form).toBeTruthy()
|
||||
|
||||
fireEvent.submit(form!)
|
||||
|
||||
expect(defaultProps.onConfirmDelete).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call onConfirmDelete when confirm on delete modal', async () => {
|
||||
const user = userEvent.setup()
|
||||
await act(async () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Confirm')).toBeInTheDocument())
|
||||
await user.click(screen.getByText('Confirm'))
|
||||
await user.type(screen.getByRole('textbox'), 'Test App')
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(defaultProps.onConfirmDelete).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@ -226,8 +232,8 @@ describe('AppInfoModals', () => {
|
||||
render(<AppInfoModals {...defaultProps} activeModal="exportWarning" />)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Confirm')).toBeInTheDocument())
|
||||
await user.click(screen.getByText('Confirm'))
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'common.operation.confirm' })).toBeInTheDocument())
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(defaultProps.handleConfirmExport).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@ -6,12 +6,21 @@ import type { App, AppSSO } from '@/types/app'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import dynamic from '@/next/dynamic'
|
||||
|
||||
const SwitchAppModal = dynamic(() => import('@/app/components/app/switch-app-modal'), { ssr: false })
|
||||
const CreateAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), { ssr: false })
|
||||
const DuplicateAppModal = dynamic(() => import('@/app/components/app/duplicate-modal'), { ssr: false })
|
||||
const Confirm = dynamic(() => import('@/app/components/base/confirm'), { ssr: false })
|
||||
const UpdateDSLModal = dynamic(() => import('@/app/components/workflow/update-dsl-modal'), { ssr: false })
|
||||
const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/dsl-export-confirm-modal'), { ssr: false })
|
||||
|
||||
@ -44,6 +53,12 @@ const AppInfoModals = ({
|
||||
}: AppInfoModalsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [confirmDeleteInput, setConfirmDeleteInput] = useState('')
|
||||
const isDeleteConfirmDisabled = confirmDeleteInput !== appDetail.name
|
||||
|
||||
const handleDeleteDialogClose = () => {
|
||||
setConfirmDeleteInput('')
|
||||
closeModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -85,39 +100,73 @@ const AppInfoModals = ({
|
||||
onHide={closeModal}
|
||||
/>
|
||||
)}
|
||||
{activeModal === 'delete' && (
|
||||
<Confirm
|
||||
title={t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||
content={t('deleteAppConfirmContent', { ns: 'app' })}
|
||||
isShow
|
||||
confirmInputLabel={t('deleteAppConfirmInputLabel', { ns: 'app', appName: appDetail.name })}
|
||||
confirmInputPlaceholder={t('deleteAppConfirmInputPlaceholder', { ns: 'app' })}
|
||||
confirmInputValue={confirmDeleteInput}
|
||||
onConfirmInputChange={setConfirmDeleteInput}
|
||||
confirmInputMatchValue={appDetail.name}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={() => {
|
||||
setConfirmDeleteInput('')
|
||||
closeModal()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={activeModal === 'delete'} onOpenChange={open => !open && handleDeleteDialogClose()}>
|
||||
<AlertDialogContent>
|
||||
<form
|
||||
className="flex flex-col"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
if (isDeleteConfirmDisabled)
|
||||
return
|
||||
onConfirmDelete()
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('deleteAppConfirmContent', { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
<div className="mt-2">
|
||||
<label className="mb-1 block system-sm-regular text-text-secondary">
|
||||
{t('deleteAppConfirmInputLabel', { ns: 'app', appName: appDetail.name })}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
placeholder={t('deleteAppConfirmInputPlaceholder', { ns: 'app' })}
|
||||
value={confirmDeleteInput}
|
||||
onChange={e => setConfirmDeleteInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton type="button">
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton type="submit" disabled={isDeleteConfirmDisabled}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</form>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{activeModal === 'importDSL' && (
|
||||
<UpdateDSLModal
|
||||
onCancel={closeModal}
|
||||
onBackup={exportCheck}
|
||||
/>
|
||||
)}
|
||||
{activeModal === 'exportWarning' && (
|
||||
<Confirm
|
||||
type="info"
|
||||
isShow
|
||||
title={t('sidebar.exportWarning', { ns: 'workflow' })}
|
||||
content={t('sidebar.exportWarningDesc', { ns: 'workflow' })}
|
||||
onConfirm={handleConfirmExport}
|
||||
onCancel={closeModal}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={activeModal === 'exportWarning'} onOpenChange={open => !open && closeModal()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('sidebar.exportWarning', { ns: 'workflow' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('sidebar.exportWarningDesc', { ns: 'workflow' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton tone="default" onClick={handleConfirmExport}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{secretEnvList.length > 0 && (
|
||||
<DSLExportConfirmModal
|
||||
envList={secretEnvList}
|
||||
|
||||
@ -137,33 +137,6 @@ vi.mock('@/app/components/datasets/rename-modal', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({
|
||||
isShow,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
title,
|
||||
content,
|
||||
}: {
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
title: string
|
||||
content: string
|
||||
}) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="confirm-dialog">
|
||||
<span>{title}</span>
|
||||
<span>{content}</span>
|
||||
<button type="button" onClick={onConfirm}>confirm</button>
|
||||
<button type="button" onClick={onCancel}>cancel</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
PortalToFollowElem: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick?: () => void }) => (
|
||||
@ -221,13 +194,13 @@ describe('Dropdown callback coverage', () => {
|
||||
await user.click(screen.getByText('common.operation.delete'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset.deleteDatasetConfirmTitle')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await user.click(screen.getByText('cancel'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('dataset.deleteDatasetConfirmTitle')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -273,6 +246,6 @@ describe('Dropdown callback coverage', () => {
|
||||
await waitFor(() => {
|
||||
expect(mockToast).toHaveBeenCalledWith('check failed', { type: 'error' })
|
||||
})
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('dataset.deleteDatasetConfirmTitle')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,8 +14,16 @@ import { useExportPipelineDSL } from '@/service/use-pipeline'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { downloadBlob } from '@/utils/download'
|
||||
import ActionButton from '../../base/action-button'
|
||||
import Confirm from '../../base/confirm'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '../../base/ui/alert-dialog'
|
||||
import RenameDatasetModal from '../../datasets/rename-modal'
|
||||
import Menu from './menu'
|
||||
|
||||
@ -135,15 +143,26 @@ const DropDown = ({
|
||||
onSuccess={refreshDataset}
|
||||
/>
|
||||
)}
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||
content={confirmMessage}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={() => setShowConfirmDelete(false)}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && setShowConfirmDelete(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{confirmMessage}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirmDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import BatchAction from '../batch-action'
|
||||
|
||||
@ -29,14 +29,28 @@ describe('BatchAction', () => {
|
||||
render(<BatchAction {...baseProps} onBatchDelete={onBatchDelete} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.delete' }))
|
||||
await screen.findByText('appAnnotation.list.delete.title')
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
expect(within(dialog).getByText('appAnnotation.list.delete.title')).toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'common.operation.delete' })[1])
|
||||
})
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'common.operation.delete' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onBatchDelete).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should hide delete confirmation when cancel is clicked', async () => {
|
||||
const onBatchDelete = vi.fn()
|
||||
render(<BatchAction {...baseProps} onBatchDelete={onBatchDelete} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.delete' }))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
expect(onBatchDelete).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,8 +3,15 @@ import { RiDeleteBinLine } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'batchAction'
|
||||
@ -41,38 +48,42 @@ const BatchAction: FC<IBatchActionProps> = ({
|
||||
return (
|
||||
<div className={cn('pointer-events-none flex w-full justify-center', className)}>
|
||||
<div className="pointer-events-auto flex items-center gap-x-1 radius-lg border border-components-actionbar-border-accent bg-components-actionbar-bg-accent p-1 shadow-xl shadow-shadow-shadow-5 backdrop-blur-[5px]">
|
||||
<div className="inline-flex items-center gap-x-2 py-1 pl-2 pr-3">
|
||||
<div className="inline-flex items-center gap-x-2 py-1 pr-3 pl-2">
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-md bg-text-accent px-1 py-0.5 text-xs font-medium text-text-primary-on-surface">
|
||||
{selectedIds.length}
|
||||
</span>
|
||||
<span className="text-[13px] font-semibold leading-[16px] text-text-accent">{t(`${i18nPrefix}.selected`, { ns: 'appAnnotation' })}</span>
|
||||
<span className="text-[13px] leading-[16px] font-semibold text-text-accent">{t(`${i18nPrefix}.selected`, { ns: 'appAnnotation' })}</span>
|
||||
</div>
|
||||
<Divider type="vertical" className="mx-0.5 h-3.5 bg-divider-regular" />
|
||||
<div className="flex cursor-pointer items-center gap-x-0.5 px-3 py-2" onClick={showDeleteConfirm}>
|
||||
<RiDeleteBinLine className="h-4 w-4 text-components-button-destructive-ghost-text" />
|
||||
<button type="button" className="px-0.5 text-[13px] font-medium leading-[16px] text-components-button-destructive-ghost-text">
|
||||
<button type="button" className="px-0.5 text-[13px] leading-[16px] font-medium text-components-button-destructive-ghost-text">
|
||||
{t('operation.delete', { ns: 'common' })}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Divider type="vertical" className="mx-0.5 h-3.5 bg-divider-regular" />
|
||||
<button type="button" className="px-3.5 py-2 text-[13px] font-medium leading-[16px] text-components-button-ghost-text" onClick={onCancel}>
|
||||
<button type="button" className="px-3.5 py-2 text-[13px] leading-[16px] font-medium text-components-button-ghost-text" onClick={onCancel}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('list.delete.title', { ns: 'appAnnotation' })}
|
||||
confirmText={t('operation.delete', { ns: 'common' })}
|
||||
onConfirm={handleBatchDelete}
|
||||
onCancel={hideDeleteConfirm}
|
||||
isLoading={isDeleting}
|
||||
isDisabled={isDeleting}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('list.delete.title', { ns: 'appAnnotation' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={isDeleting} disabled={isDeleting} onClick={handleBatchDelete}>
|
||||
{t('operation.delete', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,14 @@
|
||||
import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
|
||||
type Props = {
|
||||
isShow: boolean
|
||||
@ -17,15 +24,26 @@ const ClearAllAnnotationsConfirmModal: FC<Props> = ({
|
||||
onConfirm,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const title = t('table.header.clearAllConfirm', { ns: 'appAnnotation' })
|
||||
|
||||
return (
|
||||
<Confirm
|
||||
isShow={isShow}
|
||||
onCancel={onHide}
|
||||
onConfirm={onConfirm}
|
||||
type="danger"
|
||||
title={t('table.header.clearAllConfirm', { ns: 'appAnnotation' })}
|
||||
/>
|
||||
<AlertDialog open={isShow} onOpenChange={open => !open && onHide()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{title}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -332,6 +332,24 @@ describe('EditAnnotationModal', () => {
|
||||
// Assert
|
||||
expect(mockOnRemove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should hide confirm modal when removal is cancelled', async () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
annotationId: 'test-annotation-id',
|
||||
}
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(<EditAnnotationModal {...props} />)
|
||||
await user.click(screen.getByText('appAnnotation.editModal.removeThisCache'))
|
||||
expect(screen.getByText('appDebug.feature.annotation.removeConfirm')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('appDebug.feature.annotation.removeConfirm')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Edge Cases (REQUIRED)
|
||||
|
||||
@ -3,9 +3,16 @@ import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import AnnotationFull from '@/app/components/billing/annotation-full'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
@ -106,16 +113,33 @@ const EditAnnotationModal: FC<Props> = ({
|
||||
readonly={isAdd && isAnnotationFull}
|
||||
onSave={editedContent => handleSave(EditItemType.Answer, editedContent)}
|
||||
/>
|
||||
<Confirm
|
||||
isShow={showModal}
|
||||
onCancel={() => setShowModal(false)}
|
||||
onConfirm={() => {
|
||||
onRemove()
|
||||
setShowModal(false)
|
||||
onHide()
|
||||
}}
|
||||
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
/>
|
||||
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle
|
||||
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||
>
|
||||
{t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
tone="destructive"
|
||||
onClick={() => {
|
||||
onRemove()
|
||||
setShowModal(false)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,7 +2,14 @@
|
||||
import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
|
||||
type Props = {
|
||||
isShow: boolean
|
||||
@ -16,14 +23,26 @@ const RemoveAnnotationConfirmModal: FC<Props> = ({
|
||||
onRemove,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const title = t('feature.annotation.removeConfirm', { ns: 'appDebug' })
|
||||
|
||||
return (
|
||||
<Confirm
|
||||
isShow={isShow}
|
||||
onCancel={onHide}
|
||||
onConfirm={onRemove}
|
||||
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
/>
|
||||
<AlertDialog open={isShow} onOpenChange={open => !open && onHide()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{title}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton tone="destructive" onClick={onRemove}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(RemoveAnnotationConfirmModal)
|
||||
|
||||
@ -157,4 +157,17 @@ describe('ViewAnnotationModal', () => {
|
||||
expect(props.onHide).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should close the remove confirmation when cancelled', async () => {
|
||||
renderComponent()
|
||||
|
||||
fireEvent.click(screen.getByText('appAnnotation.editModal.removeThisCache'))
|
||||
expect(await screen.findByText('appDebug.feature.annotation.removeConfirm')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('appDebug.feature.annotation.removeConfirm')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,11 +5,18 @@ import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
|
||||
import Pagination from '@/app/components/base/pagination'
|
||||
import TabSlider from '@/app/components/base/tab-slider-plain'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { APP_PAGE_LIMIT } from '@/config'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { fetchHitHistoryList } from '@/service/annotation'
|
||||
@ -212,16 +219,33 @@ const ViewAnnotationModal: FC<Props> = ({
|
||||
<div className="space-y-6 p-6 pb-4">
|
||||
{activeTab === TabType.annotation ? annotationTab : hitHistoryTab}
|
||||
</div>
|
||||
<Confirm
|
||||
isShow={showModal}
|
||||
onCancel={() => setShowModal(false)}
|
||||
onConfirm={async () => {
|
||||
await onRemove()
|
||||
setShowModal(false)
|
||||
onHide()
|
||||
}}
|
||||
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
/>
|
||||
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle
|
||||
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||
>
|
||||
{t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
tone="destructive"
|
||||
onClick={async () => {
|
||||
await onRemove()
|
||||
setShowModal(false)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)}
|
||||
foot={id
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable ts/no-explicit-any */
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import FeaturesWrappedAppPublisher from '../features-wrapper'
|
||||
|
||||
const mockSetFeatures = vi.fn()
|
||||
@ -137,4 +137,23 @@ describe('FeaturesWrappedAppPublisher', () => {
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
it('should close restore confirmation without restoring when cancelled', async () => {
|
||||
render(
|
||||
<FeaturesWrappedAppPublisher
|
||||
publishedConfig={publishedConfig as any}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('restore-through-wrapper'))
|
||||
const dialog = screen.getByRole('alertdialog')
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
expect(publishedConfig.modelConfig.resetAppConfig).not.toHaveBeenCalled()
|
||||
expect(mockSetFeatures).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -6,9 +6,17 @@ import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppPublisher from '@/app/components/app/app-publisher'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import { Resolution } from '@/types/app'
|
||||
|
||||
@ -74,15 +82,24 @@ const FeaturesWrappedAppPublisher = (props: Props) => {
|
||||
onRestore: () => setRestoreConfirmOpen(true),
|
||||
}}
|
||||
/>
|
||||
{restoreConfirmOpen && (
|
||||
<Confirm
|
||||
title={t('resetConfig.title', { ns: 'appDebug' })}
|
||||
content={t('resetConfig.message', { ns: 'appDebug' })}
|
||||
isShow={restoreConfirmOpen}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={() => setRestoreConfirmOpen(false)}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={restoreConfirmOpen} onOpenChange={open => !open && setRestoreConfirmOpen(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('resetConfig.title', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('resetConfig.message', { ns: 'appDebug' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,8 +11,16 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ReactSortable } from 'react-sortablejs'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
@ -264,7 +272,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
||||
>
|
||||
{!hasVar && (
|
||||
<div className="mt-1 px-3 pb-3">
|
||||
<div className="pb-1 pt-2 text-xs text-text-tertiary">{t('notSetVar', { ns: 'appDebug' })}</div>
|
||||
<div className="pt-2 pb-1 text-xs text-text-tertiary">{t('notSetVar', { ns: 'appDebug' })}</div>
|
||||
</div>
|
||||
)}
|
||||
{hasVar && (
|
||||
@ -313,18 +321,29 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
||||
/>
|
||||
)}
|
||||
|
||||
{isShowDeleteContextVarModal && (
|
||||
<Confirm
|
||||
isShow={isShowDeleteContextVarModal}
|
||||
title={t('feature.dataSet.queryVariable.deleteContextVarTitle', { ns: 'appDebug', varName: promptVariables[removeIndex as number]?.name })}
|
||||
content={t('feature.dataSet.queryVariable.deleteContextVarTip', { ns: 'appDebug' })}
|
||||
onConfirm={() => {
|
||||
didRemoveVar(removeIndex as number)
|
||||
hideDeleteContextVarModal()
|
||||
}}
|
||||
onCancel={hideDeleteContextVarModal}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={isShowDeleteContextVarModal} onOpenChange={open => !open && hideDeleteContextVarModal()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('feature.dataSet.queryVariable.deleteContextVarTitle', { ns: 'appDebug', varName: promptVariables[removeIndex as number]?.name })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('feature.dataSet.queryVariable.deleteContextVarTip', { ns: 'appDebug' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
onClick={() => {
|
||||
didRemoveVar(removeIndex as number)
|
||||
hideDeleteContextVarModal()
|
||||
}}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
</Panel>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import GetAutomaticRes from '../get-automatic-res'
|
||||
|
||||
@ -237,6 +237,42 @@ describe('GetAutomaticRes', () => {
|
||||
}))
|
||||
})
|
||||
|
||||
it('should close overwrite confirmation without applying the generated result when cancelled', async () => {
|
||||
mockGenerateBasicAppFirstTimeRule.mockResolvedValue({
|
||||
prompt: 'generated prompt',
|
||||
variables: ['city'],
|
||||
opening_statement: 'hello there',
|
||||
})
|
||||
|
||||
render(
|
||||
<GetAutomaticRes
|
||||
mode={AppModeEnum.CHAT}
|
||||
isShow
|
||||
onClose={mockOnClose}
|
||||
onFinished={mockOnFinished}
|
||||
flowId="flow-1"
|
||||
isBasicMode
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('set-basic-instruction'))
|
||||
fireEvent.click(screen.getByText('generate.generate'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('result-panel')).toHaveTextContent('generated prompt')
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('apply-result'))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
expect(mockOnFinished).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should request workflow generation and surface service errors', async () => {
|
||||
mockGenerateRule.mockResolvedValue({
|
||||
error: 'generation failed',
|
||||
|
||||
@ -19,12 +19,19 @@ import { useBoolean, useSessionStorageState } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
@ -387,18 +394,29 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
)}
|
||||
{isLoading && renderLoading}
|
||||
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
|
||||
{isShowConfirmOverwrite && (
|
||||
<Confirm
|
||||
title={t('generate.overwriteTitle', { ns: 'appDebug' })}
|
||||
content={t('generate.overwriteMessage', { ns: 'appDebug' })}
|
||||
isShow
|
||||
onConfirm={() => {
|
||||
hideShowConfirmOverwrite()
|
||||
onFinished(current!)
|
||||
}}
|
||||
onCancel={hideShowConfirmOverwrite}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('generate.overwriteTitle', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('generate.overwriteMessage', { ns: 'appDebug' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
onClick={() => {
|
||||
hideShowConfirmOverwrite()
|
||||
onFinished(current!)
|
||||
}}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import GetCodeGeneratorResModal from '../get-code-generator-res'
|
||||
@ -223,6 +223,43 @@ describe('GetCodeGeneratorResModal', () => {
|
||||
}))
|
||||
})
|
||||
|
||||
it('should close overwrite confirmation without applying the generated code when cancelled', async () => {
|
||||
mockGenerateRule.mockResolvedValue({
|
||||
code: 'print("hello")',
|
||||
})
|
||||
|
||||
render(
|
||||
<GetCodeGeneratorResModal
|
||||
flowId="flow-1"
|
||||
nodeId="node-1"
|
||||
currentCode="print(1)"
|
||||
mode={AppModeEnum.CHAT}
|
||||
isShow
|
||||
codeLanguages={CodeLanguage.python3}
|
||||
onClose={mockOnClose}
|
||||
onFinished={mockOnFinished}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('set-code-instruction'))
|
||||
fireEvent.click(screen.getByText('set-code-output'))
|
||||
fireEvent.click(screen.getByText('codegen.generate'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('code-result-panel')).toHaveTextContent('print("hello")')
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('apply-code-result'))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
expect(mockOnFinished).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should surface service errors without creating a result version', async () => {
|
||||
mockGenerateRule.mockResolvedValue({
|
||||
error: 'generation failed',
|
||||
|
||||
@ -10,10 +10,18 @@ import {
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@ -263,18 +271,29 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isShowConfirmOverwrite && (
|
||||
<Confirm
|
||||
title={t('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
||||
content={t('codegen.overwriteConfirmMessage', { ns: 'appDebug' })}
|
||||
isShow
|
||||
onConfirm={() => {
|
||||
hideShowConfirmOverwrite()
|
||||
onFinished(current!)
|
||||
}}
|
||||
onCancel={hideShowConfirmOverwrite}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('codegen.overwriteConfirmMessage', { ns: 'appDebug' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
onClick={() => {
|
||||
hideShowConfirmOverwrite()
|
||||
onFinished(current!)
|
||||
}}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@ -153,28 +153,26 @@ const ConfigurationView: FC<ConfigurationViewModel> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showUseGPT4Confirm && (
|
||||
<AlertDialog open={showUseGPT4Confirm} onOpenChange={open => !open && setShowUseGPT4Confirm(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
||||
{t('trailUseGPT4Info.title', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('trailUseGPT4Info.description', { ns: 'appDebug' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton tone="default">
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton variant="primary" tone="default" onClick={onConfirmUseGPT4}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
<AlertDialog open={showUseGPT4Confirm} onOpenChange={open => !open && setShowUseGPT4Confirm(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
||||
{t('trailUseGPT4Info.title', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('trailUseGPT4Info.description', { ns: 'appDebug' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton tone="default">
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton variant="primary" tone="default" onClick={onConfirmUseGPT4}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{isShowSelectDataSet && (
|
||||
<SelectDataSet
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { AppDetailResponse } from '@/models/app'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { AppCardAccessControlSection, AppCardOperations, createAppCardOperations } from '../app-card-sections'
|
||||
import { AppCardAccessControlSection, AppCardOperations, AppCardUrlSection, createAppCardOperations } from '../app-card-sections'
|
||||
|
||||
describe('app-card-sections', () => {
|
||||
const t = (key: string) => key
|
||||
@ -100,4 +100,31 @@ describe('app-card-sections', () => {
|
||||
expect(screen.getByText('overview.appInfo.customize.entry')).toBeInTheDocument()
|
||||
expect(AppModeEnum.CHAT).toBe('chat')
|
||||
})
|
||||
|
||||
it('should invoke regenerate dialog callbacks from the url section', () => {
|
||||
const onRegenerate = vi.fn()
|
||||
const onHideRegenerateConfirm = vi.fn()
|
||||
|
||||
render(
|
||||
<AppCardUrlSection
|
||||
t={t as never}
|
||||
isApp
|
||||
accessibleUrl="https://example.com/apps/demo"
|
||||
showConfirmDelete
|
||||
isCurrentWorkspaceManager
|
||||
genLoading={false}
|
||||
onRegenerate={onRegenerate}
|
||||
onShowRegenerateConfirm={vi.fn()}
|
||||
onHideRegenerateConfirm={onHideRegenerateConfirm}
|
||||
/>,
|
||||
)
|
||||
|
||||
const dialog = screen.getByRole('alertdialog')
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /operation\.cancel/i }))
|
||||
expect(onHideRegenerateConfirm).toHaveBeenCalled()
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /operation\.confirm/i }))
|
||||
expect(onRegenerate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -180,28 +180,26 @@ export const AppCardUrlSection = ({
|
||||
<CopyFeedback content={accessibleUrl} className="size-6!" />
|
||||
{isApp && <ShareQRCode content={accessibleUrl} />}
|
||||
{isApp && <Divider type="vertical" className="mx-0.5! h-3.5! shrink-0" />}
|
||||
{showConfirmDelete && (
|
||||
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && onHideRegenerateConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-6 pb-4 pl-6">
|
||||
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
||||
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('overview.appInfo.regenerateNotice', { ns: 'appOverview' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton onClick={onHideRegenerateConfirm}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onRegenerate}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && onHideRegenerateConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-6 pb-4 pl-6">
|
||||
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
||||
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('overview.appInfo.regenerateNotice', { ns: 'appOverview' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton onClick={onHideRegenerateConfirm}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onRegenerate}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{isApp && isCurrentWorkspaceManager && (
|
||||
<MaybeTooltip content={t('overview.appInfo.regenerate', { ns: 'appOverview' }) || ''}>
|
||||
<div
|
||||
|
||||
@ -335,6 +335,7 @@ describe('SwitchAppModal', () => {
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'common.operation.confirm' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('checkbox')).not.toBeChecked()
|
||||
})
|
||||
|
||||
it('should toggle remove-original from the checkbox control itself', async () => {
|
||||
|
||||
@ -8,11 +8,19 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
@ -91,6 +99,14 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
||||
setShowConfirmDelete(true)
|
||||
}, [removeOriginal])
|
||||
|
||||
const handleConfirmDeleteOpenChange = (open: boolean) => {
|
||||
if (open)
|
||||
return
|
||||
|
||||
setShowConfirmDelete(false)
|
||||
setRemoveOriginal(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@ -156,18 +172,29 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||
content={t('deleteAppConfirmContent', { ns: 'app' })}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={() => setShowConfirmDelete(false)}
|
||||
onCancel={() => {
|
||||
setShowConfirmDelete(false)
|
||||
setRemoveOriginal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog
|
||||
open={showConfirmDelete}
|
||||
onOpenChange={handleConfirmDeleteOpenChange}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('deleteAppConfirmContent', { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={() => setShowConfirmDelete(false)}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,7 +5,15 @@ import ActionButton from '@/app/components/base/action-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { useChatWithHistoryContext } from './context'
|
||||
import MobileOperationDropdown from './header/mobile-operation-dropdown'
|
||||
import Operation from './header/operation'
|
||||
@ -78,7 +86,7 @@ const HeaderInMobile = () => {
|
||||
imageUrl={appData?.site.icon_url}
|
||||
background={appData?.site.icon_background}
|
||||
/>
|
||||
<div className="truncate text-text-secondary system-md-semibold">
|
||||
<div className="truncate system-md-semibold text-text-secondary">
|
||||
{appData?.site.title}
|
||||
</div>
|
||||
</>
|
||||
@ -121,7 +129,7 @@ const HeaderInMobile = () => {
|
||||
<div className="flex h-full w-[calc(100vw-40px)] flex-col rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-xs" onClick={e => e.stopPropagation()}>
|
||||
<div className="flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-4 py-3">
|
||||
<div className="i-custom-public-other-message-3-fill h-6 w-6 shrink-0" />
|
||||
<div className="grow text-text-secondary system-xl-semibold">{t('chat.chatSettingsTitle', { ns: 'share' })}</div>
|
||||
<div className="grow system-xl-semibold text-text-secondary">{t('chat.chatSettingsTitle', { ns: 'share' })}</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<InputsFormContent />
|
||||
@ -129,15 +137,24 @@ const HeaderInMobile = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!!showConfirm && (
|
||||
<Confirm
|
||||
title={t('chat.deleteConversation.title', { ns: 'share' })}
|
||||
content={t('chat.deleteConversation.content', { ns: 'share' }) || ''}
|
||||
isShow
|
||||
onCancel={handleCancelConfirm}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && handleCancelConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('chat.deleteConversation.title', { ns: 'share' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('chat.deleteConversation.content', { ns: 'share' }) || ''}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{showRename && (
|
||||
<RenameModal
|
||||
isShow
|
||||
|
||||
@ -10,8 +10,16 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import {
|
||||
useChatWithHistoryContext,
|
||||
@ -89,7 +97,7 @@ const Header = () => {
|
||||
/>
|
||||
</div>
|
||||
{!currentConversationId && (
|
||||
<div className={cn('grow truncate text-text-secondary system-md-semibold')}>{appData?.site.title}</div>
|
||||
<div className={cn('grow truncate system-md-semibold text-text-secondary')}>{appData?.site.title}</div>
|
||||
)}
|
||||
{currentConversationId && currentConversationItem && isSidebarCollapsed && (
|
||||
<>
|
||||
@ -141,15 +149,24 @@ const Header = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!!showConfirm && (
|
||||
<Confirm
|
||||
title={t('chat.deleteConversation.title', { ns: 'share' })}
|
||||
content={t('chat.deleteConversation.content', { ns: 'share' }) || ''}
|
||||
isShow
|
||||
onCancel={handleCancelConfirm}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && handleCancelConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('chat.deleteConversation.title', { ns: 'share' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('chat.deleteConversation.content', { ns: 'share' }) || ''}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{showRename && (
|
||||
<RenameModal
|
||||
isShow
|
||||
|
||||
@ -106,22 +106,6 @@ vi.mock('@/app/components/base/modal', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock Confirm
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ onCancel, onConfirm, title, content, isShow }: { onCancel: () => void, onConfirm: () => void, title: string, content?: React.ReactNode, isShow: boolean }) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="confirm-dialog">
|
||||
<div data-testid="confirm-title">{title}</div>
|
||||
<button data-testid="confirm-cancel" onClick={onCancel}>Cancel</button>
|
||||
<div data-testid="confirm-content">{content}</div>
|
||||
<button data-testid="confirm-confirm" onClick={onConfirm}>Confirm</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
describe('Sidebar Index', () => {
|
||||
const mockContextValue = {
|
||||
isInstalledApp: false,
|
||||
@ -475,8 +459,7 @@ describe('Sidebar Index', () => {
|
||||
render(<Sidebar />)
|
||||
|
||||
await user.click(screen.getByTestId('delete-1'))
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('confirm-title')).toBeInTheDocument()
|
||||
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call handleDeleteConversation when confirm is clicked', async () => {
|
||||
@ -490,7 +473,7 @@ describe('Sidebar Index', () => {
|
||||
render(<Sidebar />)
|
||||
|
||||
await user.click(screen.getByTestId('delete-1'))
|
||||
await user.click(screen.getByTestId('confirm-confirm'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(handleDeleteConversation).toHaveBeenCalledWith('1', expect.objectContaining({
|
||||
onSuccess: expect.any(Function),
|
||||
@ -502,11 +485,11 @@ describe('Sidebar Index', () => {
|
||||
render(<Sidebar />)
|
||||
|
||||
await user.click(screen.getByTestId('delete-1'))
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByTestId('confirm-cancel'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('share.chat.deleteConversation.title')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -525,7 +508,7 @@ describe('Sidebar Index', () => {
|
||||
render(<Sidebar />)
|
||||
|
||||
await user.click(screen.getByTestId('delete-1'))
|
||||
await user.click(screen.getByTestId('confirm-confirm'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(handleDeleteConversation).toHaveBeenCalledWith('1', expect.any(Object))
|
||||
})
|
||||
@ -837,7 +820,7 @@ describe('Sidebar Index', () => {
|
||||
|
||||
// Delete it
|
||||
await user.click(screen.getByTestId('delete-1'))
|
||||
await user.click(screen.getByTestId('confirm-confirm'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
expect(handleDeleteConversation).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@ -901,8 +884,8 @@ describe('Sidebar Index', () => {
|
||||
try {
|
||||
render(<Sidebar />)
|
||||
await user.click(screen.getByTestId('delete-1'))
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('confirm-content')).toBeEmptyDOMElement()
|
||||
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
||||
expect(screen.queryByText('share.chat.deleteConversation.content')).not.toBeInTheDocument()
|
||||
}
|
||||
finally {
|
||||
useTranslationSpy.mockRestore()
|
||||
|
||||
@ -13,9 +13,17 @@ import ActionButton from '@/app/components/base/action-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import List from '@/app/components/base/chat/chat-with-history/sidebar/list'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -167,15 +175,24 @@ const Sidebar = ({ isPanel, panelVisible }: Props) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!!showConfirm && (
|
||||
<Confirm
|
||||
title={t('chat.deleteConversation.title', { ns: 'share' })}
|
||||
content={deleteConversationContent}
|
||||
isShow
|
||||
onCancel={handleCancelConfirm}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && handleCancelConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('chat.deleteConversation.title', { ns: 'share' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{deleteConversationContent}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{showRename && (
|
||||
<RenameModal
|
||||
isShow
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import Confirm from '..'
|
||||
|
||||
vi.mock('react-dom', async () => {
|
||||
const actual = await vi.importActual<typeof import('react-dom')>('react-dom')
|
||||
|
||||
return {
|
||||
...actual,
|
||||
createPortal: (children: React.ReactNode) => children,
|
||||
}
|
||||
})
|
||||
|
||||
const onCancel = vi.fn()
|
||||
const onConfirm = vi.fn()
|
||||
|
||||
describe('Confirm Component', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders confirm correctly', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
expect(screen.getByText('test title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render on isShow false', () => {
|
||||
const { container } = render(<Confirm isShow={false} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it('hides after delay when isShow changes to false', () => {
|
||||
vi.useFakeTimers()
|
||||
const { rerender } = render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
expect(screen.getByText('test title')).toBeInTheDocument()
|
||||
|
||||
rerender(<Confirm isShow={false} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(200)
|
||||
})
|
||||
expect(screen.queryByText('test title')).not.toBeInTheDocument()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('renders content when provided', () => {
|
||||
render(<Confirm isShow={true} title="title" content="some description" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
expect(screen.getByText('some description')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
it('showCancel prop works', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} showCancel={false} />)
|
||||
expect(screen.getByRole('button', { name: 'common.operation.confirm' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.operation.cancel' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('showConfirm prop works', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} showConfirm={false} />)
|
||||
expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.operation.confirm' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders custom confirm and cancel text', () => {
|
||||
render(<Confirm isShow={true} title="title" confirmText="Yes" cancelText="No" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
expect(screen.getByRole('button', { name: 'Yes' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('disables confirm button when isDisabled is true', () => {
|
||||
render(<Confirm isShow={true} title="title" isDisabled={true} onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
expect(screen.getByRole('button', { name: 'common.operation.confirm' })).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
it('clickAway is handled properly', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
const overlay = screen.getByTestId('confirm-overlay') as HTMLElement
|
||||
expect(overlay).toBeTruthy()
|
||||
fireEvent.mouseDown(overlay)
|
||||
expect(onCancel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('overlay click stops propagation', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
const overlay = screen.getByTestId('confirm-overlay') as HTMLElement
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true })
|
||||
const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault')
|
||||
const stopPropagationSpy = vi.spyOn(clickEvent, 'stopPropagation')
|
||||
overlay.dispatchEvent(clickEvent)
|
||||
expect(preventDefaultSpy).toHaveBeenCalled()
|
||||
expect(stopPropagationSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not close on click away when maskClosable is false', () => {
|
||||
render(<Confirm isShow={true} title="test title" maskClosable={false} onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
const overlay = screen.getByTestId('confirm-overlay') as HTMLElement
|
||||
fireEvent.mouseDown(overlay)
|
||||
expect(onCancel).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('escape keyboard event works', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
fireEvent.keyDown(document, { key: 'Escape' })
|
||||
expect(onCancel).toHaveBeenCalledTimes(1)
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Enter keyboard event works', () => {
|
||||
render(<Confirm isShow={true} title="test title" onCancel={onCancel} onConfirm={onConfirm} />)
|
||||
fireEvent.keyDown(document, { key: 'Enter' })
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
expect(onCancel).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,216 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import Confirm from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Feedback/Confirm',
|
||||
component: Confirm,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Confirmation dialog component that supports warning and info types, with customizable button text and behavior.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['info', 'warning'],
|
||||
description: 'Dialog type',
|
||||
},
|
||||
isShow: {
|
||||
control: 'boolean',
|
||||
description: 'Whether to show the dialog',
|
||||
},
|
||||
title: {
|
||||
control: 'text',
|
||||
description: 'Dialog title',
|
||||
},
|
||||
content: {
|
||||
control: 'text',
|
||||
description: 'Dialog content',
|
||||
},
|
||||
confirmText: {
|
||||
control: 'text',
|
||||
description: 'Confirm button text',
|
||||
},
|
||||
cancelText: {
|
||||
control: 'text',
|
||||
description: 'Cancel button text',
|
||||
},
|
||||
isLoading: {
|
||||
control: 'boolean',
|
||||
description: 'Confirm button loading state',
|
||||
},
|
||||
isDisabled: {
|
||||
control: 'boolean',
|
||||
description: 'Confirm button disabled state',
|
||||
},
|
||||
showConfirm: {
|
||||
control: 'boolean',
|
||||
description: 'Whether to show confirm button',
|
||||
},
|
||||
showCancel: {
|
||||
control: 'boolean',
|
||||
description: 'Whether to show cancel button',
|
||||
},
|
||||
maskClosable: {
|
||||
control: 'boolean',
|
||||
description: 'Whether clicking mask closes dialog',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
onConfirm: () => {
|
||||
console.log('✅ User clicked confirm')
|
||||
},
|
||||
onCancel: () => {
|
||||
console.log('❌ User clicked cancel')
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Confirm>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Interactive demo wrapper
|
||||
const ConfirmDemo = (args: any) => {
|
||||
const [isShow, setIsShow] = useState(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="primary" onClick={() => setIsShow(true)}>
|
||||
Open Dialog
|
||||
</Button>
|
||||
<Confirm
|
||||
{...args}
|
||||
isShow={isShow}
|
||||
onConfirm={() => {
|
||||
console.log('✅ User clicked confirm')
|
||||
setIsShow(false)
|
||||
}}
|
||||
onCancel={() => {
|
||||
console.log('❌ User clicked cancel')
|
||||
setIsShow(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Basic warning dialog - Delete action
|
||||
export const WarningDialog: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'warning',
|
||||
title: 'Delete Confirmation',
|
||||
content: 'Are you sure you want to delete this project? This action cannot be undone.',
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Info dialog
|
||||
export const InfoDialog: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'info',
|
||||
title: 'Notice',
|
||||
content: 'Your changes have been saved. Do you want to proceed to the next step?',
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Custom button text
|
||||
export const CustomButtonText: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'warning',
|
||||
title: 'Exit Editor',
|
||||
content: 'You have unsaved changes. Are you sure you want to exit?',
|
||||
confirmText: 'Discard Changes',
|
||||
cancelText: 'Continue Editing',
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Loading state
|
||||
export const LoadingState: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'warning',
|
||||
title: 'Deleting...',
|
||||
content: 'Please wait while we delete the file...',
|
||||
isLoading: true,
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Disabled state
|
||||
export const DisabledState: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'info',
|
||||
title: 'Verification Required',
|
||||
content: 'Please complete email verification before proceeding.',
|
||||
isDisabled: true,
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Alert style - Confirm button only
|
||||
export const AlertStyle: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'info',
|
||||
title: 'Success',
|
||||
content: 'Your settings have been updated!',
|
||||
showCancel: false,
|
||||
confirmText: 'Got it',
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Dangerous action - Long content
|
||||
export const DangerousAction: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'warning',
|
||||
title: 'Permanently Delete Account',
|
||||
content: 'This action will permanently delete your account and all associated data, including: all projects and files, collaboration history, and personal settings. This action cannot be reversed!',
|
||||
confirmText: 'Delete My Account',
|
||||
cancelText: 'Keep My Account',
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Non-closable mask
|
||||
export const NotMaskClosable: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'warning',
|
||||
title: 'Important Action',
|
||||
content: 'This action requires your explicit choice. Clicking outside will not close this dialog.',
|
||||
maskClosable: false,
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Full feature demo - Playground
|
||||
export const Playground: Story = {
|
||||
render: args => <ConfirmDemo {...args} />,
|
||||
args: {
|
||||
type: 'warning',
|
||||
title: 'This is a title',
|
||||
content: 'This is the dialog content text...',
|
||||
confirmText: undefined,
|
||||
cancelText: undefined,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
showConfirm: true,
|
||||
showCancel: true,
|
||||
maskClosable: true,
|
||||
isShow: false,
|
||||
},
|
||||
}
|
||||
@ -1,164 +0,0 @@
|
||||
/**
|
||||
* @deprecated Use `@/app/components/base/ui/alert-dialog` instead.
|
||||
* See issue #32767 for migration details.
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import Tooltip from '../tooltip'
|
||||
|
||||
/** @deprecated Use `@/app/components/base/ui/alert-dialog` instead. */
|
||||
export type IConfirm = {
|
||||
className?: string
|
||||
isShow: boolean
|
||||
type?: 'info' | 'warning' | 'danger'
|
||||
title: string
|
||||
content?: React.ReactNode
|
||||
confirmText?: string | null
|
||||
onConfirm: () => void
|
||||
cancelText?: string
|
||||
onCancel: () => void
|
||||
isLoading?: boolean
|
||||
isDisabled?: boolean
|
||||
showConfirm?: boolean
|
||||
showCancel?: boolean
|
||||
maskClosable?: boolean
|
||||
confirmInputLabel?: string
|
||||
confirmInputPlaceholder?: string
|
||||
confirmInputValue?: string
|
||||
onConfirmInputChange?: (value: string) => void
|
||||
confirmInputMatchValue?: string
|
||||
}
|
||||
|
||||
function Confirm({
|
||||
isShow,
|
||||
type = 'warning',
|
||||
title,
|
||||
content,
|
||||
confirmText,
|
||||
cancelText,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
showConfirm = true,
|
||||
showCancel = true,
|
||||
isLoading = false,
|
||||
isDisabled = false,
|
||||
maskClosable = true,
|
||||
confirmInputLabel,
|
||||
confirmInputPlaceholder,
|
||||
confirmInputValue = '',
|
||||
onConfirmInputChange,
|
||||
confirmInputMatchValue,
|
||||
}: IConfirm) {
|
||||
const { t } = useTranslation()
|
||||
const dialogRef = useRef<HTMLDivElement>(null)
|
||||
const titleRef = useRef<HTMLDivElement>(null)
|
||||
const [isVisible, setIsVisible] = useState(isShow)
|
||||
const [isTitleTruncated, setIsTitleTruncated] = useState(false)
|
||||
|
||||
const confirmTxt = confirmText || `${t('operation.confirm', { ns: 'common' })}`
|
||||
const cancelTxt = cancelText || `${t('operation.cancel', { ns: 'common' })}`
|
||||
const isConfirmDisabled = isDisabled || (confirmInputMatchValue ? confirmInputValue !== confirmInputMatchValue : false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape')
|
||||
onCancel()
|
||||
if (event.key === 'Enter' && isShow && !isConfirmDisabled) {
|
||||
event.preventDefault()
|
||||
onConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [onCancel, onConfirm, isShow, isConfirmDisabled])
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (maskClosable && dialogRef.current && !dialogRef.current.contains(event.target as Node))
|
||||
onCancel()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [maskClosable])
|
||||
|
||||
useEffect(() => {
|
||||
if (isShow) {
|
||||
setIsVisible(true)
|
||||
}
|
||||
else {
|
||||
const timer = setTimeout(() => setIsVisible(false), 200)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [isShow])
|
||||
|
||||
useEffect(() => {
|
||||
if (titleRef.current) {
|
||||
const isOverflowing = titleRef.current.scrollWidth > titleRef.current.clientWidth
|
||||
setIsTitleTruncated(isOverflowing)
|
||||
}
|
||||
}, [title, isVisible])
|
||||
|
||||
if (!isVisible)
|
||||
return null
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-10000000 flex items-center justify-center bg-background-overlay"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
data-testid="confirm-overlay"
|
||||
>
|
||||
<div ref={dialogRef} className="relative w-full max-w-[480px] overflow-hidden">
|
||||
<div className="shadows-shadow-lg flex max-w-full flex-col items-start rounded-2xl border-[0.5px] border-solid border-components-panel-border bg-components-panel-bg">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-6 pb-4 pl-6">
|
||||
<Tooltip
|
||||
popupContent={title}
|
||||
disabled={!isTitleTruncated}
|
||||
portalContentClassName="z-10000001!"
|
||||
asChild={false}
|
||||
triggerClassName="w-full"
|
||||
>
|
||||
<div ref={titleRef} className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{title}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">{content}</div>
|
||||
{confirmInputLabel && (
|
||||
<div className="mt-2">
|
||||
<label className="mb-1 block system-sm-regular text-text-secondary">
|
||||
{confirmInputLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="border-components-input-border bg-components-input-bg focus:border-components-input-border-focus focus:ring-components-input-border-focus h-9 w-full rounded-lg border px-3 text-sm text-text-primary placeholder:text-text-quaternary focus:ring-1 focus:outline-hidden"
|
||||
placeholder={confirmInputPlaceholder}
|
||||
value={confirmInputValue}
|
||||
onChange={e => onConfirmInputChange?.(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-start justify-end gap-2 self-stretch p-6">
|
||||
{showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}
|
||||
{showConfirm && <Button variant="primary" tone={type !== 'info' ? 'destructive' : 'default'} loading={isLoading} disabled={isConfirmDisabled} onClick={onConfirm}>{confirmTxt}</Button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Confirm)
|
||||
@ -3,8 +3,16 @@ import type { Tag } from '@/app/components/base/tag-management/constant'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { deleteTag, updateTag } from '@/service/tag'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -118,16 +126,34 @@ const TagItemEditor: FC<TagItemEditorProps> = ({ tag }) => {
|
||||
)}
|
||||
{isEditing && (<input className="shrink-0 appearance-none caret-primary-600 outline-none placeholder:text-text-quaternary" autoFocus value={name} onChange={e => setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && editTag(tag.id, name)} onBlur={() => editTag(tag.id, name)} />)}
|
||||
</div>
|
||||
<Confirm
|
||||
title={`${t('tag.delete', { ns: 'common' })} "${tag.name}"`}
|
||||
isShow={showRemoveModal}
|
||||
content={t('tag.deleteTip', { ns: 'common' })}
|
||||
onConfirm={() => {
|
||||
handleRemove()
|
||||
setShowRemoveModal(false)
|
||||
}}
|
||||
onCancel={() => setShowRemoveModal(false)}
|
||||
/>
|
||||
<AlertDialog open={showRemoveModal} onOpenChange={open => !open && setShowRemoveModal(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle
|
||||
title={`${t('tag.delete', { ns: 'common' })} "${tag.name}"`}
|
||||
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||
>
|
||||
{`${t('tag.delete', { ns: 'common' })} "${tag.name}"`}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('tag.deleteTip', { ns: 'common' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
onClick={() => {
|
||||
handleRemove()
|
||||
setShowRemoveModal(false)
|
||||
}}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -37,33 +37,6 @@ vi.mock('@/utils/download', () => ({
|
||||
downloadUrl: vi.fn(),
|
||||
}))
|
||||
|
||||
// Capture Confirm callbacks
|
||||
let _capturedOnConfirm: (() => void) | undefined
|
||||
let _capturedOnCancel: (() => void) | undefined
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onConfirm, onCancel, title, content }: {
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
title: string
|
||||
content: string
|
||||
}) => {
|
||||
_capturedOnConfirm = onConfirm
|
||||
_capturedOnCancel = onCancel
|
||||
return isShow
|
||||
? (
|
||||
<div data-testid="confirm-dialog">
|
||||
<div data-testid="confirm-title">{title}</div>
|
||||
<div data-testid="confirm-content">{content}</div>
|
||||
<button data-testid="confirm-cancel" onClick={onCancel}>Cancel</button>
|
||||
<button data-testid="confirm-submit" onClick={onConfirm}>Confirm</button>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
},
|
||||
}))
|
||||
|
||||
// Capture Actions callbacks
|
||||
let _capturedHandleDelete: (() => void) | undefined
|
||||
let _capturedHandleExportDSL: (() => void) | undefined
|
||||
@ -182,13 +155,14 @@ describe('TemplateCard', () => {
|
||||
type: 'customized' as const,
|
||||
}
|
||||
|
||||
const getDeleteConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockToastSuccess.mockReset()
|
||||
mockToastError.mockReset()
|
||||
mockIsExporting = false
|
||||
_capturedOnConfirm = undefined
|
||||
_capturedOnCancel = undefined
|
||||
_capturedHandleDelete = undefined
|
||||
_capturedHandleExportDSL = undefined
|
||||
_capturedOpenEditModal = undefined
|
||||
@ -507,7 +481,7 @@ describe('TemplateCard', () => {
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -517,14 +491,13 @@ describe('TemplateCard', () => {
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const cancelButton = screen.getByTestId('confirm-cancel')
|
||||
fireEvent.click(cancelButton)
|
||||
fireEvent.click(getDeleteCancelButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('datasetPipeline.deletePipeline.title')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -539,11 +512,10 @@ describe('TemplateCard', () => {
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const confirmButton = screen.getByTestId('confirm-submit')
|
||||
fireEvent.click(confirmButton)
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeletePipeline).toHaveBeenCalledWith('pipeline-1', expect.any(Object))
|
||||
@ -561,11 +533,10 @@ describe('TemplateCard', () => {
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const confirmButton = screen.getByTestId('confirm-submit')
|
||||
fireEvent.click(confirmButton)
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockInvalidCustomizedTemplateList).toHaveBeenCalled()
|
||||
@ -583,14 +554,13 @@ describe('TemplateCard', () => {
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const confirmButton = screen.getByTestId('confirm-submit')
|
||||
fireEvent.click(confirmButton)
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('datasetPipeline.deletePipeline.title')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,8 +3,16 @@ import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -156,15 +164,24 @@ const TemplateCard = ({
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{showDeleteConfirm && (
|
||||
<Confirm
|
||||
title={t('deletePipeline.title', { ns: 'datasetPipeline' })}
|
||||
content={t('deletePipeline.content', { ns: 'datasetPipeline' })}
|
||||
isShow={showDeleteConfirm}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={onCancelDelete}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && onCancelDelete()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('deletePipeline.title', { ns: 'datasetPipeline' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('deletePipeline.content', { ns: 'datasetPipeline' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirmDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{showDetailModal && (
|
||||
<Modal
|
||||
isShow={showDetailModal}
|
||||
|
||||
@ -7,12 +7,20 @@ import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import CustomPopover from '@/app/components/base/popover'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { DataSourceType, DocumentActionType } from '@/models/datasets'
|
||||
@ -158,7 +166,7 @@ const Operations = ({ embeddingAvailable, datasetId, detail, selectedIds, onSele
|
||||
</Tooltip>
|
||||
)
|
||||
: <Switch value={enabled} onChange={v => handleSwitch(v ? 'enable' : 'disable')} size="md" />}
|
||||
<Divider className="!ml-4 !mr-2 !h-3" type="vertical" />
|
||||
<Divider className="!mr-2 !ml-4 !h-3" type="vertical" />
|
||||
</>
|
||||
)}
|
||||
{embeddingAvailable && (
|
||||
@ -280,8 +288,24 @@ const Operations = ({ embeddingAvailable, datasetId, detail, selectedIds, onSele
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showModal
|
||||
&& (<Confirm isShow={showModal} isLoading={deleting} isDisabled={deleting} title={t('list.delete.title', { ns: 'datasetDocuments' })} content={t('list.delete.content', { ns: 'datasetDocuments' })} confirmText={t('operation.sure', { ns: 'common' })} onConfirm={() => onOperate('delete')} onCancel={() => setShowModal(false)} />)}
|
||||
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('list.delete.content', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={() => onOperate('delete')}>
|
||||
{t('operation.sure', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{isShowRenameModal && currDocument && (<RenameModal datasetId={datasetId} documentId={currDocument.id} name={currDocument.name} onClose={setShowRenameModalFalse} onSaved={handleRenamed} />)}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import BatchAction from '../batch-action'
|
||||
|
||||
@ -105,6 +105,21 @@ describe('BatchAction', () => {
|
||||
expect(mockOnBatchDelete).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should close delete confirmation when cancel is clicked', async () => {
|
||||
const mockOnBatchDelete = vi.fn()
|
||||
render(<BatchAction {...defaultProps} onBatchDelete={mockOnBatchDelete} />)
|
||||
|
||||
fireEvent.click(screen.getByText(/batchAction\.delete/i))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
expect(mockOnBatchDelete).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// Optional props tests
|
||||
|
||||
@ -3,10 +3,18 @@ import { RiArchive2Line, RiCheckboxCircleLine, RiCloseCircleLine, RiDeleteBinLin
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
@ -147,20 +155,24 @@ const BatchAction: FC<IBatchActionProps> = ({
|
||||
<span className="px-0.5">{t(`${i18nPrefix}.cancel`, { ns: 'dataset' })}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
content={t('list.delete.content', { ns: 'datasetDocuments' })}
|
||||
confirmText={t('operation.sure', { ns: 'common' })}
|
||||
onConfirm={handleBatchDelete}
|
||||
onCancel={hideDeleteConfirm}
|
||||
isLoading={isDeleting}
|
||||
isDisabled={isDeleting}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('list.delete.content', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={isDeleting} disabled={isDeleting} onClick={handleBatchDelete}>
|
||||
{t('operation.sure', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,10 +5,17 @@ import * as React from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import ImageList from '@/app/components/datasets/common/image-list'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -137,7 +144,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
data-testid="segment-card"
|
||||
className={cn(
|
||||
'chunk-card group/card w-full rounded-xl px-3',
|
||||
isFullDocMode ? '' : 'pb-2 pt-2.5 hover:bg-dataset-chunk-detail-card-hover-bg',
|
||||
isFullDocMode ? '' : 'pt-2.5 pb-2 hover:bg-dataset-chunk-detail-card-hover-bg',
|
||||
focused.segmentContent ? 'bg-dataset-chunk-detail-card-hover-bg' : '',
|
||||
className,
|
||||
)}
|
||||
@ -170,7 +177,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
<div className="flex items-center">
|
||||
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
|
||||
{embeddingAvailable && (
|
||||
<div className="absolute -right-2.5 -top-2 z-20 hidden items-center gap-x-0.5 radius-lg border-[0.5px]
|
||||
<div className="absolute -top-2 -right-2.5 z-20 hidden items-center gap-x-0.5 radius-lg border-[0.5px]
|
||||
border-components-actionbar-border bg-components-actionbar-bg p-1 shadow-md backdrop-blur-[5px] group-hover/card:flex"
|
||||
>
|
||||
{!archived && (
|
||||
@ -254,7 +261,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
className="system-xs-semibold-uppercase mb-2 mt-0.5 text-text-accent"
|
||||
className="mt-0.5 mb-2 system-xs-semibold-uppercase text-text-accent"
|
||||
onClick={() => onClick?.()}
|
||||
>
|
||||
{t('operation.viewMore', { ns: 'common' })}
|
||||
@ -276,16 +283,21 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{showModal
|
||||
&& (
|
||||
<Confirm
|
||||
isShow={showModal}
|
||||
title={t('segment.delete', { ns: 'datasetDocuments' })}
|
||||
confirmText={t('operation.sure', { ns: 'common' })}
|
||||
onConfirm={async () => { await onDelete?.(id) }}
|
||||
onCancel={() => setShowModal(false)}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('segment.delete', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={async () => { await onDelete?.(id) }}>
|
||||
{t('operation.sure', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,9 +4,17 @@ import { RiBook2Line, RiCloseLine, RiInformation2Line, RiLock2Fill } from '@remi
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent } from '@/app/components/base/portal-to-follow-elem'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { createExternalAPI } from '@/service/datasets'
|
||||
@ -176,7 +184,27 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCan
|
||||
{t('externalAPIForm.encrypted.end', { ns: 'dataset' })}
|
||||
</div>
|
||||
</div>
|
||||
{showConfirm && (datasetBindings?.length ?? 0) > 0 && (<Confirm isShow={showConfirm} type="warning" title="Warning" content={`${t('editExternalAPIConfirmWarningContent.front', { ns: 'dataset' })} ${datasetBindings?.length} ${t('editExternalAPIConfirmWarningContent.end', { ns: 'dataset' })}`} onCancel={() => setShowConfirm(false)} onConfirm={handleSave} />)}
|
||||
<AlertDialog
|
||||
open={showConfirm && (datasetBindings?.length ?? 0) > 0}
|
||||
onOpenChange={open => !open && setShowConfirm(false)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
Warning
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{`${t('editExternalAPIConfirmWarningContent.front', { ns: 'dataset' })} ${datasetBindings?.length} ${t('editExternalAPIConfirmWarningContent.end', { ns: 'dataset' })}`}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleSave}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
||||
@ -8,8 +8,16 @@ import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { checkUsageExternalAPI, deleteExternalAPI, fetchExternalAPI, updateExternalAPI } from '@/service/datasets'
|
||||
@ -115,7 +123,7 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({ api
|
||||
<ApiConnectionMod className="h-4 w-4" />
|
||||
<div className="system-sm-medium">{api.name}</div>
|
||||
</div>
|
||||
<div className="system-xs-regular self-stretch text-text-tertiary">{api.settings.endpoint}</div>
|
||||
<div className="self-stretch system-xs-regular text-text-tertiary">{api.settings.endpoint}</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-1">
|
||||
<ActionButton onClick={handleEditClick}>
|
||||
@ -131,20 +139,26 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({ api
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{showConfirm && (
|
||||
<Confirm
|
||||
isShow={showConfirm}
|
||||
title={`${t('deleteExternalAPIConfirmWarningContent.title.front', { ns: 'dataset' })} ${api.name}${t('deleteExternalAPIConfirmWarningContent.title.end', { ns: 'dataset' })}`}
|
||||
content={
|
||||
usageCount > 0
|
||||
? `${t('deleteExternalAPIConfirmWarningContent.content.front', { ns: 'dataset' })} ${usageCount} ${t('deleteExternalAPIConfirmWarningContent.content.end', { ns: 'dataset' })}`
|
||||
: t('deleteExternalAPIConfirmWarningContent.noConnectionContent', { ns: 'dataset' })
|
||||
}
|
||||
type="warning"
|
||||
onConfirm={handleConfirmDelete}
|
||||
onCancel={() => setShowConfirm(false)}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={showConfirm} onOpenChange={open => !open && setShowConfirm(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{`${t('deleteExternalAPIConfirmWarningContent.title.front', { ns: 'dataset' })} ${api.name}${t('deleteExternalAPIConfirmWarningContent.title.end', { ns: 'dataset' })}`}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{usageCount > 0
|
||||
? `${t('deleteExternalAPIConfirmWarningContent.content.front', { ns: 'dataset' })} ${usageCount} ${t('deleteExternalAPIConfirmWarningContent.content.end', { ns: 'dataset' })}`
|
||||
: t('deleteExternalAPIConfirmWarningContent.noConnectionContent', { ns: 'dataset' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleConfirmDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -19,28 +19,6 @@ vi.mock('../../../../rename-modal', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Confirm component since it uses createPortal which can cause issues in tests
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, title, content, onConfirm, onCancel }: {
|
||||
isShow: boolean
|
||||
title: string
|
||||
content?: React.ReactNode
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}) => (
|
||||
isShow
|
||||
? (
|
||||
<div data-testid="confirm-modal">
|
||||
<div data-testid="confirm-title">{title}</div>
|
||||
<div data-testid="confirm-content">{content}</div>
|
||||
<button onClick={onCancel} role="button" aria-label="cancel">Cancel</button>
|
||||
<button onClick={onConfirm} role="button" aria-label="confirm">Confirm</button>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
),
|
||||
}))
|
||||
|
||||
describe('DatasetCardModals', () => {
|
||||
const mockDataset: DataSet = {
|
||||
id: 'dataset-1',
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import RenameDatasetModal from '../../../rename-modal'
|
||||
|
||||
type ModalState = {
|
||||
@ -39,15 +47,26 @@ const DatasetCardModals = ({
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
)}
|
||||
{modalState.showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||
content={modalState.confirmMessage}
|
||||
isShow={modalState.showConfirmDelete}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={onCloseConfirm}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={modalState.showConfirmDelete} onOpenChange={open => !open && onCloseConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{modalState.confirmMessage}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirmDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,13 +6,21 @@ import { useBoolean, useHover } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import CreateModal from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -95,16 +103,24 @@ const Item: FC<ItemProps> = ({
|
||||
<RiDeleteBinLine className="size-4 cursor-pointer" onClick={showDeleteConfirm} />
|
||||
</div>
|
||||
</div>
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
type="warning"
|
||||
title={t('metadata.datasetMetadata.deleteTitle', { ns: 'dataset' })}
|
||||
content={t('metadata.datasetMetadata.deleteContent', { ns: 'dataset', name: payload.name })}
|
||||
onConfirm={handleDelete}
|
||||
onCancel={hideDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('metadata.datasetMetadata.deleteTitle', { ns: 'dataset' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('metadata.datasetMetadata.deleteContent', { ns: 'dataset', name: payload.name })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { afterEach } from 'vitest'
|
||||
import SecretKeyModal from '../secret-key-modal'
|
||||
@ -477,6 +477,32 @@ describe('SecretKeyModal', () => {
|
||||
|
||||
expect(mockDelAppApikey).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should close confirm dialog when Escape is pressed', async () => {
|
||||
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime })
|
||||
await renderModal(<SecretKeyModal {...defaultProps} appId="app-123" />)
|
||||
|
||||
const actionButtons = document.body.querySelectorAll('button.action-btn')
|
||||
const deleteButton = actionButtons[1]
|
||||
await act(async () => {
|
||||
await user.click(deleteButton!)
|
||||
vi.runAllTimers()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('appApi.actionMsg.deleteConfirmTitle')).toBeInTheDocument()
|
||||
})
|
||||
await flushTransitions()
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' })
|
||||
vi.runAllTimers()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('appApi.actionMsg.deleteConfirmTitle')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete key for dataset', () => {
|
||||
|
||||
@ -7,11 +7,19 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import {
|
||||
@ -87,6 +95,14 @@ const SecretKeyModal = ({
|
||||
return `${token.slice(0, 3)}...${token.slice(-20)}`
|
||||
}
|
||||
|
||||
const handleDeleteConfirmOpenChange = (open: boolean) => {
|
||||
if (open)
|
||||
return
|
||||
|
||||
setDelKeyId('')
|
||||
setShowConfirmDelete(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={onClose} title={`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`} className={`${s.customModal} flex flex-col px-8`}>
|
||||
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
||||
@ -135,18 +151,29 @@ const SecretKeyModal = ({
|
||||
</Button>
|
||||
</div>
|
||||
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={`${t('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}`}
|
||||
content={`${t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}`}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={onDel}
|
||||
onCancel={() => {
|
||||
setDelKeyId('')
|
||||
setShowConfirmDelete(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog
|
||||
open={showConfirmDelete}
|
||||
onOpenChange={handleDeleteConfirmOpenChange}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onDel}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@ -105,8 +105,10 @@ describe('Item Component', () => {
|
||||
|
||||
// Act
|
||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||
const dialog = screen.getByTestId('confirm-overlay')
|
||||
const confirmButton = within(dialog).getByText('common.operation.delete')
|
||||
const dialog = screen.getByRole('alertdialog', {
|
||||
name: /common\.operation\.delete.*Test Extension.*\?/i,
|
||||
})
|
||||
const confirmButton = within(dialog).getByRole('button', { name: 'common.operation.delete' })
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
// Assert
|
||||
@ -123,8 +125,10 @@ describe('Item Component', () => {
|
||||
|
||||
// Act
|
||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||
const dialog = screen.getByTestId('confirm-overlay')
|
||||
const confirmButton = within(dialog).getByText('common.operation.delete')
|
||||
const dialog = screen.getByRole('alertdialog', {
|
||||
name: /common\.operation\.delete.*Test Extension.*\?/i,
|
||||
})
|
||||
const confirmButton = within(dialog).getByRole('button', { name: 'common.operation.delete' })
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
// Assert
|
||||
@ -133,14 +137,16 @@ describe('Item Component', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should close delete confirmation when clicking cancel button', () => {
|
||||
it('should close delete confirmation when clicking cancel button', async () => {
|
||||
// Act
|
||||
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText(/common\.operation\.delete.*Test Extension.*\?/i)).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/common\.operation\.delete.*Test Extension.*\?/i)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not call delete API when canceling deletion', () => {
|
||||
|
||||
@ -6,7 +6,14 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { deleteApiBasedExtension } from '@/service/common'
|
||||
@ -57,18 +64,21 @@ const Item: FC<ItemProps> = ({
|
||||
{t('operation.delete', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
showDeleteConfirm
|
||||
&& (
|
||||
<Confirm
|
||||
isShow={showDeleteConfirm}
|
||||
onCancel={() => setShowDeleteConfirm(false)}
|
||||
title={`${t('operation.delete', { ns: 'common' })} “${data.name}”?`}
|
||||
onConfirm={handleDeleteApiBasedExtension}
|
||||
confirmText={t('operation.delete', { ns: 'common' }) || ''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && setShowDeleteConfirm(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{`${t('operation.delete', { ns: 'common' })} \u201C${data.name}\u201D?`}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleDeleteApiBasedExtension}>
|
||||
{t('operation.delete', { ns: 'common' }) || ''}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,7 +8,14 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import {
|
||||
ApiKeyModal,
|
||||
usePluginAuthAction,
|
||||
@ -123,7 +130,7 @@ const Card = ({
|
||||
<div className="system-md-semibold text-text-primary">
|
||||
{renderI18nObject(label)}
|
||||
</div>
|
||||
<div className="system-xs-regular flex h-4 items-center text-text-tertiary">
|
||||
<div className="flex h-4 items-center system-xs-regular text-text-tertiary">
|
||||
{author}
|
||||
<div className="mx-0.5 text-text-quaternary">/</div>
|
||||
{name}
|
||||
@ -135,7 +142,7 @@ const Card = ({
|
||||
onUpdate={handleAuthUpdate}
|
||||
/>
|
||||
</div>
|
||||
<div className="system-xs-medium flex h-4 items-center pl-3 text-text-tertiary">
|
||||
<div className="flex h-4 items-center pl-3 system-xs-medium text-text-tertiary">
|
||||
{t('auth.connectedWorkspace', { ns: 'plugin' })}
|
||||
<div className="ml-3 h-px grow bg-divider-subtle"></div>
|
||||
</div>
|
||||
@ -157,23 +164,27 @@ const Card = ({
|
||||
{
|
||||
!credentials_list.length && (
|
||||
<div className="p-3 pt-1">
|
||||
<div className="system-xs-regular flex h-10 items-center justify-center radius-lg bg-background-section text-text-tertiary">
|
||||
<div className="flex h-10 items-center justify-center radius-lg bg-background-section system-xs-regular text-text-tertiary">
|
||||
{t('auth.emptyAuth', { ns: 'plugin' })}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
deleteCredentialId && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
isDisabled={doingAction}
|
||||
onCancel={closeConfirm}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={!!deleteCredentialId} onOpenChange={open => !open && closeConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton disabled={doingAction} onClick={handleConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{
|
||||
!!editValues && (
|
||||
<ApiKeyModal
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Credential, CustomModel, ModelProvider } from '../../../declarations'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react'
|
||||
import { ConfigurationMethodEnum, ModelTypeEnum } from '../../../declarations'
|
||||
import Authorized from '../index'
|
||||
|
||||
@ -198,4 +198,39 @@ describe('Authorized', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: /common.operation.confirm/i }))
|
||||
expect(mockHandleConfirmDelete).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should close confirm dialog when deletion is cancelled', () => {
|
||||
mockDeleteCredentialId = 'cred-1'
|
||||
|
||||
render(
|
||||
<Authorized
|
||||
provider={mockProvider}
|
||||
configurationMethod={ConfigurationMethodEnum.predefinedModel}
|
||||
items={mockItems}
|
||||
renderTrigger={mockRenderTrigger}
|
||||
/>,
|
||||
)
|
||||
|
||||
const dialog = screen.getByRole('alertdialog')
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /operation.cancel/i }))
|
||||
|
||||
expect(mockCloseConfirmDelete).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should disable the confirm button while deletion is in progress', () => {
|
||||
mockDeleteCredentialId = 'cred-1'
|
||||
mockDoingAction = true
|
||||
|
||||
render(
|
||||
<Authorized
|
||||
provider={mockProvider}
|
||||
configurationMethod={ConfigurationMethodEnum.predefinedModel}
|
||||
items={mockItems}
|
||||
renderTrigger={mockRenderTrigger}
|
||||
/>,
|
||||
)
|
||||
|
||||
const dialog = screen.getByRole('alertdialog')
|
||||
expect(within(dialog).getByRole('button', { name: /common.operation.confirm/i })).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -19,12 +19,19 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useAuth } from '../hooks'
|
||||
@ -240,17 +247,21 @@ const Authorized = ({
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
{
|
||||
deleteCredentialId && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('modelProvider.confirmDelete', { ns: 'common' })}
|
||||
isDisabled={doingAction}
|
||||
onCancel={closeConfirmDelete}
|
||||
onConfirm={handleConfirmDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={!!deleteCredentialId} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('modelProvider.confirmDelete', { ns: 'common' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton disabled={doingAction} onClick={handleConfirmDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import type { Credential, CustomConfigurationModelFixedFields, ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
@ -266,7 +273,21 @@ const ModelLoadBalancingModal = ({ provider, configurateMethod, currentCustomCon
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
{deleteModel && (<Confirm isShow title={t('modelProvider.confirmDelete', { ns: 'common' })} onCancel={closeConfirmDelete} onConfirm={handleDeleteModel} isDisabled={doingAction} />)}
|
||||
<AlertDialog open={!!deleteModel} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('modelProvider.confirmDelete', { ns: 'common' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton disabled={doingAction} onClick={handleDeleteModel}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,12 +12,19 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
@ -318,17 +325,21 @@ const Authorized = ({
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
{
|
||||
deleteCredentialId && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
isDisabled={doingAction}
|
||||
onCancel={closeConfirm}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={!!deleteCredentialId} onOpenChange={open => !open && closeConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton disabled={doingAction} onClick={handleConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{
|
||||
!!editValues && (
|
||||
<ApiKeyModal
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { EndpointListItem, PluginDetail } from '../../types'
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import EndpointCard from '../endpoint-card'
|
||||
|
||||
@ -136,7 +136,6 @@ const mockPluginDetail: PluginDetail = {
|
||||
describe('EndpointCard', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers()
|
||||
// Reset failure flags
|
||||
failureFlags.enable = false
|
||||
failureFlags.disable = false
|
||||
@ -152,6 +151,12 @@ describe('EndpointCard', () => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
const waitForAlertDialogToClose = async () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
}
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render endpoint name', () => {
|
||||
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||
@ -243,6 +248,7 @@ describe('EndpointCard', () => {
|
||||
|
||||
describe('Copy Functionality', () => {
|
||||
it('should reset copy state after timeout', async () => {
|
||||
vi.useFakeTimers()
|
||||
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||
|
||||
const allButtons = screen.getAllByRole('button')
|
||||
@ -276,19 +282,19 @@ describe('EndpointCard', () => {
|
||||
expect(mockHandleChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should hide disable confirm and revert state when cancel clicked', () => {
|
||||
it('should hide disable confirm and revert state when cancel clicked', async () => {
|
||||
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('switch'))
|
||||
expect(screen.getByText('plugin.detailPanel.endpointDisableTip')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
await waitForAlertDialogToClose()
|
||||
|
||||
// Confirm should be hidden
|
||||
expect(screen.queryByText('plugin.detailPanel.endpointDisableTip')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true')
|
||||
})
|
||||
|
||||
it('should hide delete confirm when cancel clicked', () => {
|
||||
it('should hide delete confirm when cancel clicked', async () => {
|
||||
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||
|
||||
const allButtons = screen.getAllByRole('button')
|
||||
@ -296,8 +302,7 @@ describe('EndpointCard', () => {
|
||||
expect(screen.getByText('plugin.detailPanel.endpointDeleteTip')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(screen.queryByText('plugin.detailPanel.endpointDeleteTip')).not.toBeInTheDocument()
|
||||
await waitForAlertDialogToClose()
|
||||
})
|
||||
|
||||
it('should hide edit modal when cancel clicked', () => {
|
||||
|
||||
@ -6,10 +6,17 @@ import * as React from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
@ -122,6 +129,14 @@ const EndpointCard = ({
|
||||
setIsCopied(true)
|
||||
}
|
||||
|
||||
const handleDisableConfirmOpenChange = (open: boolean) => {
|
||||
if (open)
|
||||
return
|
||||
|
||||
hideDisableConfirm()
|
||||
setActive(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isCopied) {
|
||||
const timer = setTimeout(() => {
|
||||
@ -139,7 +154,7 @@ const EndpointCard = ({
|
||||
<div className="rounded-xl bg-background-section-burn p-0.5">
|
||||
<div className="group radius-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-2.5 pl-3">
|
||||
<div className="flex items-center">
|
||||
<div className="mb-1 flex h-6 grow items-center gap-1 text-text-secondary system-md-semibold">
|
||||
<div className="mb-1 flex h-6 grow items-center gap-1 system-md-semibold text-text-secondary">
|
||||
<RiLoginCircleLine className="h-4 w-4" />
|
||||
<div>{data.name}</div>
|
||||
</div>
|
||||
@ -154,8 +169,8 @@ const EndpointCard = ({
|
||||
</div>
|
||||
{data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
|
||||
<div key={index} className="flex h-6 items-center">
|
||||
<div className="w-12 shrink-0 text-text-tertiary system-xs-regular">{endpoint.method}</div>
|
||||
<div className="group/item flex grow items-center truncate text-text-secondary system-xs-regular">
|
||||
<div className="w-12 shrink-0 system-xs-regular text-text-tertiary">{endpoint.method}</div>
|
||||
<div className="group/item flex grow items-center truncate system-xs-regular text-text-secondary">
|
||||
<div title={`${data.url}${endpoint.path}`} className="truncate">{`${data.url}${endpoint.path}`}</div>
|
||||
<Tooltip popupContent={t(`operation.${isCopied ? 'copied' : 'copy'}`, { ns: 'common' })} position="top">
|
||||
<ActionButton className="ml-2 hidden shrink-0 group-hover/item:flex" onClick={() => handleCopy(`${data.url}${endpoint.path}`)}>
|
||||
@ -168,13 +183,13 @@ const EndpointCard = ({
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-2 pl-3">
|
||||
{active && (
|
||||
<div className="flex items-center gap-1 text-util-colors-green-green-600 system-xs-semibold-uppercase">
|
||||
<div className="flex items-center gap-1 system-xs-semibold-uppercase text-util-colors-green-green-600">
|
||||
<Indicator color="green" />
|
||||
{t('detailPanel.serviceOk', { ns: 'plugin' })}
|
||||
</div>
|
||||
)}
|
||||
{!active && (
|
||||
<div className="flex items-center gap-1 text-text-tertiary system-xs-semibold-uppercase">
|
||||
<div className="flex items-center gap-1 system-xs-semibold-uppercase text-text-tertiary">
|
||||
<Indicator color="gray" />
|
||||
{t('detailPanel.disabled', { ns: 'plugin' })}
|
||||
</div>
|
||||
@ -186,27 +201,47 @@ const EndpointCard = ({
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
{isShowDisableConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('detailPanel.endpointDisableTip', { ns: 'plugin' })}
|
||||
content={<div>{t('detailPanel.endpointDisableContent', { ns: 'plugin', name: data.name })}</div>}
|
||||
onCancel={() => {
|
||||
hideDisableConfirm()
|
||||
setActive(true)
|
||||
}}
|
||||
onConfirm={() => disableEndpoint(endpointID)}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('detailPanel.endpointDeleteTip', { ns: 'plugin' })}
|
||||
content={<div>{t('detailPanel.endpointDeleteContent', { ns: 'plugin', name: data.name })}</div>}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={() => deleteEndpoint(endpointID)}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog
|
||||
open={isShowDisableConfirm}
|
||||
onOpenChange={handleDisableConfirmOpenChange}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('detailPanel.endpointDisableTip', { ns: 'plugin' })}
|
||||
</AlertDialogTitle>
|
||||
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('detailPanel.endpointDisableContent', { ns: 'plugin', name: data.name })}
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={() => disableEndpoint(endpointID)}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('detailPanel.endpointDeleteTip', { ns: 'plugin' })}
|
||||
</AlertDialogTitle>
|
||||
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('detailPanel.endpointDeleteContent', { ns: 'plugin', name: data.name })}
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={() => deleteEndpoint(endpointID)}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{isShowEndpointModal && (
|
||||
<EndpointModal
|
||||
formSchemas={formSchemas as any}
|
||||
|
||||
@ -59,18 +59,18 @@ export const DeleteConfirm = (props: Props) => {
|
||||
return (
|
||||
<AlertDialog open={isShow} onOpenChange={handleOpenChange}>
|
||||
<AlertDialogContent backdropProps={{ forceRender: true }}>
|
||||
<div className="flex flex-col gap-2 px-6 pb-4 pt-6">
|
||||
<AlertDialogTitle title={t(`${tPrefix}.title`, { ns: 'pluginTrigger', name: currentName })} className="w-full truncate text-text-primary title-2xl-semi-bold">
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t(`${tPrefix}.title`, { ns: 'pluginTrigger', name: currentName })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full whitespace-pre-wrap wrap-break-word text-text-tertiary system-md-regular">
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{workflowsInUse > 0
|
||||
? t(`${tPrefix}.contentWithApps`, { ns: 'pluginTrigger', count: workflowsInUse })
|
||||
: t(`${tPrefix}.content`, { ns: 'pluginTrigger' })}
|
||||
</AlertDialogDescription>
|
||||
{workflowsInUse > 0 && (
|
||||
<div className="mt-6">
|
||||
<div className="mb-2 text-text-secondary system-sm-medium">
|
||||
<div className="mb-2 system-sm-medium text-text-secondary">
|
||||
{t(`${tPrefix}.confirmInputTip`, { ns: 'pluginTrigger', name: currentName })}
|
||||
</div>
|
||||
<Input
|
||||
|
||||
@ -92,30 +92,6 @@ vi.mock('../../../base/tooltip', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Confirm - uses createPortal which has issues in test environment
|
||||
vi.mock('../../../base/confirm', () => ({
|
||||
default: ({ isShow, title, content, onCancel, onConfirm, isLoading, isDisabled }: {
|
||||
isShow: boolean
|
||||
title: string
|
||||
content: React.ReactNode
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
isLoading: boolean
|
||||
isDisabled: boolean
|
||||
}) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="confirm-modal" data-loading={isLoading} data-disabled={isDisabled}>
|
||||
<div data-testid="confirm-title">{title}</div>
|
||||
<div data-testid="confirm-content">{content}</div>
|
||||
<button data-testid="confirm-cancel" onClick={onCancel}>Cancel</button>
|
||||
<button data-testid="confirm-ok" onClick={onConfirm} disabled={isDisabled}>Confirm</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
// ==================== Test Utilities ====================
|
||||
|
||||
type ActionProps = {
|
||||
@ -151,6 +127,9 @@ const createActionProps = (overrides: Partial<ActionProps> = {}): ActionProps =>
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const getDeleteConfirmButton = () => screen.getByRole('button', { name: /common\.operation\.confirm/ })
|
||||
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||
|
||||
// ==================== Tests ====================
|
||||
|
||||
// Helper to find action buttons (real ActionButton component uses type="button")
|
||||
@ -277,8 +256,7 @@ describe('Action Component', () => {
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('confirm-modal')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('confirm-title')).toHaveTextContent('plugin.action.delete')
|
||||
expect(screen.getByText('plugin.action.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display plugin name in delete confirm content', () => {
|
||||
@ -309,12 +287,14 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
expect(screen.getByTestId('confirm-modal')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.action.delete')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-cancel'))
|
||||
fireEvent.click(getDeleteCancelButton())
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
||||
return waitFor(() => {
|
||||
expect(screen.queryByText('plugin.action.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call uninstallPlugin when confirm is clicked', async () => {
|
||||
@ -329,7 +309,7 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
@ -351,7 +331,7 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
@ -373,7 +353,7 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
@ -395,7 +375,7 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
@ -422,17 +402,17 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
// Assert - Loading state
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-modal')).toHaveAttribute('data-loading', 'true')
|
||||
expect(getDeleteConfirmButton()).toBeDisabled()
|
||||
})
|
||||
|
||||
// Resolve and check modal closes
|
||||
resolveUninstall!({ success: true })
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.action.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -699,7 +679,7 @@ describe('Action Component', () => {
|
||||
// Act - First render and delete
|
||||
const { rerender } = render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('stable-install-id')
|
||||
@ -709,7 +689,7 @@ describe('Action Component', () => {
|
||||
mockUninstallPlugin.mockClear()
|
||||
rerender(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('stable-install-id')
|
||||
@ -735,7 +715,7 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
const { rerender } = render(<Action {...props1} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('install-1')
|
||||
@ -744,7 +724,7 @@ describe('Action Component', () => {
|
||||
mockUninstallPlugin.mockClear()
|
||||
rerender(<Action {...props2} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('install-2')
|
||||
@ -772,7 +752,7 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
const { rerender } = render(<Action {...props1} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onDelete1).toHaveBeenCalled()
|
||||
@ -781,7 +761,7 @@ describe('Action Component', () => {
|
||||
|
||||
rerender(<Action {...props2} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onDelete2).toHaveBeenCalled()
|
||||
@ -847,17 +827,16 @@ describe('Action Component', () => {
|
||||
// Act
|
||||
render(<Action {...props} />)
|
||||
fireEvent.click(getActionButtons()[0])
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
// The confirm button should be disabled during deletion
|
||||
expect(screen.getByTestId('confirm-modal')).toHaveAttribute('data-loading', 'true')
|
||||
expect(screen.getByTestId('confirm-modal')).toHaveAttribute('data-disabled', 'true')
|
||||
expect(getDeleteConfirmButton()).toBeDisabled()
|
||||
|
||||
// Resolve the deletion
|
||||
resolveFirst!({ success: true })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.action.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -7,12 +7,19 @@ import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import ActionButton from '../../base/action-button'
|
||||
import Confirm from '../../base/confirm'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import { checkForUpdates, fetchReleases } from '../install-plugin/hooks'
|
||||
import PluginInfo from '../plugin-page/plugin-info'
|
||||
@ -151,24 +158,27 @@ const Action: FC<Props> = ({
|
||||
onHide={hidePluginInfo}
|
||||
/>
|
||||
)}
|
||||
<Confirm
|
||||
isShow={isShowDeleteConfirm}
|
||||
title={t(`${i18nPrefix}.delete`, { ns: 'plugin' })}
|
||||
content={(
|
||||
<div>
|
||||
{t(`${i18nPrefix}.deleteContentLeft`, { ns: 'plugin' })}
|
||||
<span className="system-md-semibold">{pluginName}</span>
|
||||
{t(`${i18nPrefix}.deleteContentRight`, { ns: 'plugin' })}
|
||||
<br />
|
||||
{/* // todo: add usedInApps */}
|
||||
{/* {usedInApps > 0 && t(`${i18nPrefix}.usedInApps`, { num: usedInApps })} */}
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.delete`, { ns: 'plugin' })}
|
||||
</AlertDialogTitle>
|
||||
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t(`${i18nPrefix}.deleteContentLeft`, { ns: 'plugin' })}
|
||||
<span className="system-md-semibold">{pluginName}</span>
|
||||
{t(`${i18nPrefix}.deleteContentRight`, { ns: 'plugin' })}
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={handleDelete}
|
||||
isLoading={deleting}
|
||||
isDisabled={deleting}
|
||||
/>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import Conversion from '../conversion'
|
||||
@ -47,29 +47,6 @@ vi.mock('@/app/components/base/ui/button', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({
|
||||
isShow,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
title,
|
||||
}: {
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
title: string
|
||||
}) =>
|
||||
isShow
|
||||
? (
|
||||
<div data-testid="confirm-modal">
|
||||
<span>{title}</span>
|
||||
<button data-testid="confirm-btn" onClick={onConfirm}>Confirm</button>
|
||||
<button data-testid="cancel-btn" onClick={onCancel}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
}))
|
||||
|
||||
vi.mock('../screenshot', () => ({
|
||||
default: () => <div data-testid="screenshot" />,
|
||||
}))
|
||||
@ -112,11 +89,10 @@ describe('Conversion', () => {
|
||||
it('should show confirm modal when convert button clicked', () => {
|
||||
render(<Conversion />)
|
||||
|
||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('datasetPipeline.conversion.confirm.title')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||
|
||||
expect(screen.getByTestId('confirm-modal')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -124,17 +100,19 @@ describe('Conversion', () => {
|
||||
render(<Conversion />)
|
||||
|
||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||
expect(screen.getByTestId('confirm-modal')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByTestId('cancel-btn'))
|
||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
return waitFor(() => {
|
||||
expect(screen.queryByText('datasetPipeline.conversion.confirm.title')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call convert when confirm is clicked', () => {
|
||||
render(<Conversion />)
|
||||
|
||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(mockConvert).toHaveBeenCalledWith('ds-123', expect.objectContaining({
|
||||
onSuccess: expect.any(Function),
|
||||
@ -150,7 +128,7 @@ describe('Conversion', () => {
|
||||
render(<Conversion />)
|
||||
|
||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(mockToast.success).toHaveBeenCalledWith('datasetPipeline.conversion.successMessage')
|
||||
expect(mockInvalidDatasetDetail).toHaveBeenCalled()
|
||||
@ -164,7 +142,7 @@ describe('Conversion', () => {
|
||||
render(<Conversion />)
|
||||
|
||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(mockToast.error).toHaveBeenCalledWith('datasetPipeline.conversion.errorMessage')
|
||||
})
|
||||
@ -177,7 +155,7 @@ describe('Conversion', () => {
|
||||
render(<Conversion />)
|
||||
|
||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(mockToast.error).toHaveBeenCalledWith('datasetPipeline.conversion.errorMessage')
|
||||
})
|
||||
|
||||
@ -472,21 +472,23 @@ describe('Conversion', () => {
|
||||
const convertButton = screen.getByRole('button', { name: /datasetPipeline\.operations\.convert/i })
|
||||
fireEvent.click(convertButton)
|
||||
|
||||
// Real Confirm renders title and content via portal
|
||||
// AlertDialog renders title and content via portal.
|
||||
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetPipeline.conversion.confirm.content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide confirm modal when cancel is clicked', () => {
|
||||
it('should hide confirm modal when cancel is clicked', async () => {
|
||||
render(<Conversion />)
|
||||
|
||||
const convertButton = screen.getByRole('button', { name: /datasetPipeline\.operations\.convert/i })
|
||||
fireEvent.click(convertButton)
|
||||
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
||||
|
||||
// Real Confirm renders cancel button with i18n text
|
||||
// AlertDialog close is async because it unmounts after state updates.
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
expect(screen.queryByText('datasetPipeline.conversion.confirm.title')).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('datasetPipeline.conversion.confirm.title')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useParams } from '@/next/navigation'
|
||||
@ -39,6 +47,8 @@ const Conversion = () => {
|
||||
const handleCancelConversion = useCallback(() => {
|
||||
setShowConfirmModal(false)
|
||||
}, [])
|
||||
const confirmTitle = t('conversion.confirm.title', { ns: 'datasetPipeline' })
|
||||
const confirmContent = t('conversion.confirm.content', { ns: 'datasetPipeline' })
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-background-body p-6 pb-16">
|
||||
<div className="flex rounded-2xl border-[0.5px] border-components-card-border bg-components-card-bg shadow-sm shadow-shadow-shadow-4">
|
||||
@ -69,7 +79,26 @@ const Conversion = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showConfirmModal && (<Confirm title={t('conversion.confirm.title', { ns: 'datasetPipeline' })} content={t('conversion.confirm.content', { ns: 'datasetPipeline' })} isShow={showConfirmModal} onConfirm={handleConvert} onCancel={handleCancelConversion} isLoading={isPending} isDisabled={isPending} />)}
|
||||
<AlertDialog open={showConfirmModal} onOpenChange={open => !open && handleCancelConversion()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{confirmTitle}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{confirmContent}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={isPending} disabled={isPending} onClick={handleConvert}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -688,15 +688,11 @@ describe('publisher', () => {
|
||||
expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const cancelButtons = screen.getAllByRole('button')
|
||||
const cancelButton = cancelButtons.find(btn =>
|
||||
btn.className.includes('cancel') || btn.textContent?.includes('Cancel'),
|
||||
)
|
||||
if (cancelButton)
|
||||
fireEvent.click(cancelButton)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
// Note: This test verifies the confirm modal can be displayed
|
||||
expect(screen.getByText('pipeline.common.confirmPublishContent')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should publish when confirm is clicked in confirm modal', async () => {
|
||||
@ -711,7 +707,11 @@ describe('publisher', () => {
|
||||
expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
expect(screen.getByText('pipeline.common.confirmPublishContent')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockPublishWorkflow).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -87,24 +87,6 @@ vi.mock('@/app/components/base/ui/button', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onConfirm, onCancel, title }: {
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
title: string
|
||||
}) =>
|
||||
isShow
|
||||
? (
|
||||
<div data-testid="confirm-modal">
|
||||
<span>{title}</span>
|
||||
<button data-testid="publish-confirm" onClick={onConfirm}>OK</button>
|
||||
<button data-testid="publish-cancel" onClick={onCancel}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/divider', () => ({
|
||||
default: () => <hr />,
|
||||
}))
|
||||
|
||||
@ -5,10 +5,18 @@ import { useBoolean, useKeyPress } from 'ahooks'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useChecklistBeforePublish } from '@/app/components/workflow/hooks'
|
||||
@ -224,7 +232,29 @@ const Popup = () => {
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{confirmVisible && (<Confirm isShow={confirmVisible} title={t('common.confirmPublish', { ns: 'pipeline' })} content={t('common.confirmPublishContent', { ns: 'pipeline' })} onCancel={hideConfirm} onConfirm={handlePublish} isDisabled={publishing} />)}
|
||||
<AlertDialog open={confirmVisible} onOpenChange={open => !open && hideConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle
|
||||
title={t('common.confirmPublish', { ns: 'pipeline' })}
|
||||
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||
>
|
||||
{t('common.confirmPublish', { ns: 'pipeline' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('common.confirmPublishContent', { ns: 'pipeline' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton disabled={publishing} onClick={() => void handlePublish()}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{showPublishAsKnowledgePipelineModal && (<PublishAsKnowledgePipelineModal confirmDisabled={isPublishingAsCustomizedPipeline} onConfirm={handlePublishAsKnowledgePipeline} onCancel={hidePublishAsKnowledgePipelineModal} />)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -49,31 +49,6 @@ vi.mock('../modal', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the Confirm dialog
|
||||
type ConfirmDialogProps = {
|
||||
isShow: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onConfirm, onCancel, isLoading }: ConfirmDialogProps) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="confirm-dialog">
|
||||
<button data-testid="confirm-delete-btn" onClick={onConfirm} disabled={isLoading}>
|
||||
{isLoading ? 'Deleting...' : 'Confirm Delete'}
|
||||
</button>
|
||||
<button data-testid="cancel-delete-btn" onClick={onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the OperationDropdown
|
||||
type OperationDropdownProps = {
|
||||
onEdit: () => void
|
||||
@ -172,6 +147,9 @@ describe('MCPCard', () => {
|
||||
onDeleted: vi.fn(),
|
||||
}
|
||||
|
||||
const getDeleteConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||
|
||||
beforeEach(() => {
|
||||
mockUpdateMCP.mockClear()
|
||||
mockDeleteMCP.mockClear()
|
||||
@ -450,7 +428,7 @@ describe('MCPCard', () => {
|
||||
|
||||
// Confirm dialog should be shown
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -462,15 +440,14 @@ describe('MCPCard', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Cancel
|
||||
const cancelBtn = screen.getByTestId('cancel-delete-btn')
|
||||
fireEvent.click(cancelBtn)
|
||||
fireEvent.click(getDeleteCancelButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('tools.mcp.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -483,12 +460,11 @@ describe('MCPCard', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Confirm delete
|
||||
const confirmBtn = screen.getByTestId('confirm-delete-btn')
|
||||
fireEvent.click(confirmBtn)
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteMCP).toHaveBeenCalledWith('mcp-1')
|
||||
@ -506,12 +482,11 @@ describe('MCPCard', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Confirm delete
|
||||
const confirmBtn = screen.getByTestId('confirm-delete-btn')
|
||||
fireEvent.click(confirmBtn)
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteMCP).toHaveBeenCalled()
|
||||
|
||||
@ -84,20 +84,6 @@ vi.mock('../../modal', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock Confirm dialog
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onConfirm, onCancel, title }: { isShow: boolean, onConfirm: () => void, onCancel: () => void, title: string }) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="confirm-dialog" data-title={title}>
|
||||
<button data-testid="confirm-btn" onClick={onConfirm}>Confirm</button>
|
||||
<button data-testid="cancel-btn" onClick={onCancel}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock OperationDropdown
|
||||
vi.mock('../operation-dropdown', () => ({
|
||||
default: ({ onEdit, onRemove }: { onEdit: () => void, onRemove: () => void }) => (
|
||||
@ -165,6 +151,9 @@ describe('MCPDetailContent', () => {
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children)
|
||||
}
|
||||
|
||||
const getConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||
const getCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||
|
||||
const createMockDetail = (overrides = {}): ToolWithProvider => ({
|
||||
id: 'mcp-1',
|
||||
name: 'Test MCP Server',
|
||||
@ -494,7 +483,7 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(updateBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.toolUpdateConfirmTitle')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -514,12 +503,11 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(updateBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.toolUpdateConfirmTitle')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Confirm the update
|
||||
const confirmBtn = screen.getByTestId('confirm-btn')
|
||||
fireEvent.click(confirmBtn)
|
||||
fireEvent.click(getConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTools).toHaveBeenCalledWith('mcp-1')
|
||||
@ -636,7 +624,7 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -648,15 +636,14 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Cancel
|
||||
const cancelBtn = screen.getByTestId('cancel-btn')
|
||||
fireEvent.click(cancelBtn)
|
||||
fireEvent.click(getCancelButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('tools.mcp.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -669,12 +656,11 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Confirm delete
|
||||
const confirmBtn = screen.getByTestId('confirm-btn')
|
||||
fireEvent.click(confirmBtn)
|
||||
fireEvent.click(getConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteMCP).toHaveBeenCalledWith('mcp-1')
|
||||
@ -692,12 +678,11 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(removeBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Confirm delete
|
||||
const confirmBtn = screen.getByTestId('confirm-btn')
|
||||
fireEvent.click(confirmBtn)
|
||||
fireEvent.click(getConfirmButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteMCP).toHaveBeenCalled()
|
||||
@ -840,15 +825,14 @@ describe('MCPDetailContent', () => {
|
||||
fireEvent.click(updateBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.mcp.toolUpdateConfirmTitle')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Cancel the update
|
||||
const cancelBtn = screen.getByTestId('cancel-btn')
|
||||
fireEvent.click(cancelBtn)
|
||||
fireEvent.click(getCancelButton())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('tools.mcp.toolUpdateConfirmTitle')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -12,8 +12,16 @@ import * as React from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||
@ -286,30 +294,42 @@ const MCPDetailContent: FC<Props> = ({
|
||||
onHide={hideUpdateModal}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('mcp.delete', { ns: 'tools' })}
|
||||
content={(
|
||||
<div>
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('mcp.delete', { ns: 'tools' })}
|
||||
</AlertDialogTitle>
|
||||
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('mcp.deleteConfirmTitle', { ns: 'tools', mcp: detail.name })}
|
||||
</div>
|
||||
)}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={handleDelete}
|
||||
isLoading={deleting}
|
||||
isDisabled={deleting}
|
||||
/>
|
||||
)}
|
||||
{isShowUpdateConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('mcp.toolUpdateConfirmTitle', { ns: 'tools' })}
|
||||
content={t('mcp.toolUpdateConfirmContent', { ns: 'tools' })}
|
||||
onCancel={hideUpdateConfirm}
|
||||
onConfirm={handleUpdateTools}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog open={isShowUpdateConfirm} onOpenChange={open => !open && hideUpdateConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('mcp.toolUpdateConfirmTitle', { ns: 'tools' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('mcp.toolUpdateConfirmContent', { ns: 'tools' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleUpdateTools}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,12 +6,20 @@ import type { AppSSO } from '@/types/app'
|
||||
import { RiEditLine, RiLoopLeftLine } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal'
|
||||
@ -295,16 +303,24 @@ const MCPServiceCard: FC<IAppCardProps> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
type="warning"
|
||||
title={t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||
content={t('mcp.server.reGen', { ns: 'tools' })}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={onConfirmRegenerate}
|
||||
onCancel={closeConfirmDelete}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('mcp.server.reGen', { ns: 'tools' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirmRegenerate}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,7 +4,14 @@ import { RiHammerFill } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
@ -87,34 +94,34 @@ const MCPCard = ({
|
||||
<Icon src={data.icon} />
|
||||
</div>
|
||||
<div className="grow">
|
||||
<div className="system-md-semibold mb-1 truncate text-text-secondary" title={data.name}>{data.name}</div>
|
||||
<div className="mb-1 truncate system-md-semibold text-text-secondary" title={data.name}>{data.name}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{data.server_identifier}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 rounded-b-xl pb-2.5 pl-4 pr-2.5 pt-1.5">
|
||||
<div className="flex items-center gap-1 rounded-b-xl pt-1.5 pr-2.5 pb-2.5 pl-4">
|
||||
<div className="flex w-0 grow items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<RiHammerFill className="h-3 w-3 shrink-0 text-text-quaternary" />
|
||||
{data.tools.length > 0 && (
|
||||
<div className="system-xs-regular shrink-0 text-text-tertiary">{t('mcp.toolsCount', { ns: 'tools', count: data.tools.length })}</div>
|
||||
<div className="shrink-0 system-xs-regular text-text-tertiary">{t('mcp.toolsCount', { ns: 'tools', count: data.tools.length })}</div>
|
||||
)}
|
||||
{!data.tools.length && (
|
||||
<div className="system-xs-regular shrink-0 text-text-tertiary">{t('mcp.noTools', { ns: 'tools' })}</div>
|
||||
<div className="shrink-0 system-xs-regular text-text-tertiary">{t('mcp.noTools', { ns: 'tools' })}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn('system-xs-regular text-divider-deep', (!data.is_team_authorization || !data.tools.length) && 'sm:hidden')}>/</div>
|
||||
<div className={cn('system-xs-regular truncate text-text-tertiary', (!data.is_team_authorization || !data.tools.length) && ' sm:hidden')} title={`${t('mcp.updateTime', { ns: 'tools' })} ${formatTimeFromNow(data.updated_at! * 1000)}`}>{`${t('mcp.updateTime', { ns: 'tools' })} ${formatTimeFromNow(data.updated_at! * 1000)}`}</div>
|
||||
<div className={cn('truncate system-xs-regular text-text-tertiary', (!data.is_team_authorization || !data.tools.length) && 'sm:hidden')} title={`${t('mcp.updateTime', { ns: 'tools' })} ${formatTimeFromNow(data.updated_at! * 1000)}`}>{`${t('mcp.updateTime', { ns: 'tools' })} ${formatTimeFromNow(data.updated_at! * 1000)}`}</div>
|
||||
</div>
|
||||
{data.is_team_authorization && data.tools.length > 0 && <Indicator color="green" className="shrink-0" />}
|
||||
{(!data.is_team_authorization || !data.tools.length) && (
|
||||
<div className="system-xs-medium flex shrink-0 items-center gap-1 rounded-md border border-util-colors-red-red-500 bg-components-badge-bg-red-soft px-1.5 py-0.5 text-util-colors-red-red-500">
|
||||
<div className="flex shrink-0 items-center gap-1 rounded-md border border-util-colors-red-red-500 bg-components-badge-bg-red-soft px-1.5 py-0.5 system-xs-medium text-util-colors-red-red-500">
|
||||
{t('mcp.noConfigured', { ns: 'tools' })}
|
||||
<Indicator color="red" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isCurrentWorkspaceManager && (
|
||||
<div className={cn('absolute right-2.5 top-2.5 hidden group-hover:block', isOperationShow && 'block')} onClick={e => e.stopPropagation()}>
|
||||
<div className={cn('absolute top-2.5 right-2.5 hidden group-hover:block', isOperationShow && 'block')} onClick={e => e.stopPropagation()}>
|
||||
<OperationDropdown
|
||||
inCard
|
||||
onOpenChange={setIsOperationShow}
|
||||
@ -131,21 +138,24 @@ const MCPCard = ({
|
||||
onHide={hideUpdateModal}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('mcp.delete', { ns: 'tools' })}
|
||||
content={(
|
||||
<div>
|
||||
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('mcp.delete', { ns: 'tools' })}
|
||||
</AlertDialogTitle>
|
||||
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('mcp.deleteConfirmTitle', { ns: 'tools', mcp: data.name })}
|
||||
</div>
|
||||
)}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={handleDelete}
|
||||
isLoading={deleting}
|
||||
isDisabled={deleting}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={handleDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -79,19 +79,6 @@ vi.mock('@/app/components/base/drawer', () => ({
|
||||
isOpen ? <div data-testid="drawer">{children}</div> : null,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onConfirm, onCancel, title }: { isShow: boolean, onConfirm: () => void, onCancel: () => void, title: string }) =>
|
||||
isShow
|
||||
? (
|
||||
<div data-testid="confirm-dialog">
|
||||
<span>{title}</span>
|
||||
<button data-testid="confirm-btn" onClick={onConfirm}>Confirm</button>
|
||||
<button data-testid="cancel-btn" onClick={onCancel}>Cancel</button>
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
}))
|
||||
|
||||
const mockToastSuccess = vi.hoisted(() => vi.fn())
|
||||
const mockToastError = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
@ -170,6 +157,9 @@ const createMockCollection = (overrides?: Partial<Collection>): Collection => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const getDeleteConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||
|
||||
describe('ProviderDetail', () => {
|
||||
const mockOnHide = vi.fn()
|
||||
const mockOnRefreshData = vi.fn()
|
||||
@ -552,9 +542,9 @@ describe('ProviderDetail', () => {
|
||||
})
|
||||
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
||||
fireEvent.click(screen.getByTestId('edit-remove'))
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.deleteToolConfirmTitle')).toBeInTheDocument()
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(mockRemoveCustomCollection).toHaveBeenCalledWith('test-collection')
|
||||
@ -626,9 +616,9 @@ describe('ProviderDetail', () => {
|
||||
})
|
||||
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
||||
fireEvent.click(screen.getByTestId('wf-remove'))
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.deleteToolConfirmTitle')).toBeInTheDocument()
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
||||
fireEvent.click(getDeleteConfirmButton())
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteWorkflowTool).toHaveBeenCalledWith('test-id')
|
||||
@ -710,9 +700,11 @@ describe('ProviderDetail', () => {
|
||||
})
|
||||
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
||||
fireEvent.click(screen.getByTestId('edit-remove'))
|
||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByTestId('cancel-btn'))
|
||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.deleteToolConfirmTitle')).toBeInTheDocument()
|
||||
fireEvent.click(getDeleteCancelButton())
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,10 +8,18 @@ import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { Button } from '@/app/components/base/ui/button'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@ -401,15 +409,24 @@ const ProviderDetail = ({
|
||||
onSave={updateWorkflowToolProvider}
|
||||
/>
|
||||
)}
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('createTool.deleteToolConfirmTitle', { ns: 'tools' })}
|
||||
content={t('createTool.deleteToolConfirmContent', { ns: 'tools' })}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onCancel={() => setShowConfirmDelete(false)}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && setShowConfirmDelete(false)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{t('createTool.deleteToolConfirmTitle', { ns: 'tools' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('createTool.deleteToolConfirmContent', { ns: 'tools' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleConfirmDelete}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</Drawer>
|
||||
)
|
||||
|
||||
@ -30,10 +30,10 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
|
||||
|
||||
<div className="pb-4">
|
||||
<div className="mb-6">
|
||||
<DialogTitle className="mb-2 text-text-primary title-2xl-semi-bold">
|
||||
<DialogTitle className="mb-2 title-2xl-semi-bold text-text-primary">
|
||||
{t('onboarding.title', { ns: 'workflow' })}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="leading-4 text-text-tertiary body-xs-regular">
|
||||
<DialogDescription className="body-xs-regular leading-4 text-text-tertiary">
|
||||
{t('onboarding.description', { ns: 'workflow' })}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
@ -47,7 +47,7 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
|
||||
|
||||
{/* TODO: reduce z-1002 to match base/ui primitives after legacy overlay migration completes */}
|
||||
<DialogPortal>
|
||||
<div className="pointer-events-none fixed left-1/2 top-1/2 z-1002 flex -translate-x-1/2 translate-y-[165px] items-center gap-1 text-text-quaternary body-xs-regular">
|
||||
<div className="pointer-events-none fixed top-1/2 left-1/2 z-1002 flex -translate-x-1/2 translate-y-[165px] items-center gap-1 body-xs-regular text-text-quaternary">
|
||||
<span>{t('onboarding.escTip.press', { ns: 'workflow' })}</span>
|
||||
<ShortcutsName keys={[t('onboarding.escTip.key', { ns: 'workflow' })]} textColor="secondary" />
|
||||
<span>{t('onboarding.escTip.toDismiss', { ns: 'workflow' })}</span>
|
||||
|
||||
@ -408,4 +408,46 @@ describe('Workflow edge event wiring', () => {
|
||||
|
||||
expect(store.getState().edgeMenu).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should render confirm description and clear showConfirm when cancelled', async () => {
|
||||
const onConfirm = vi.fn()
|
||||
const { store } = renderSubject({
|
||||
initialStoreState: {
|
||||
showConfirm: {
|
||||
title: 'Confirm title',
|
||||
desc: 'Confirm description',
|
||||
onConfirm,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('Confirm title')).toBeInTheDocument()
|
||||
expect(screen.getByText('Confirm description')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
expect(store.getState().showConfirm).toBeUndefined()
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call showConfirm.onConfirm when confirm is clicked', () => {
|
||||
const onConfirm = vi.fn()
|
||||
|
||||
renderSubject({
|
||||
initialStoreState: {
|
||||
showConfirm: {
|
||||
title: 'Confirm title',
|
||||
onConfirm,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ReactFlowProvider,
|
||||
@ -34,9 +35,17 @@ import ReactFlow, {
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { IS_DEV } from '@/config'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import {
|
||||
useAllBuiltInTools,
|
||||
useAllCustomTools,
|
||||
@ -103,10 +112,6 @@ import { WorkflowHistoryProvider } from './workflow-history-store'
|
||||
import 'reactflow/dist/style.css'
|
||||
import './style.css'
|
||||
|
||||
const Confirm = dynamic(() => import('@/app/components/base/confirm'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
const nodeTypes = {
|
||||
[CUSTOM_NODE]: CustomNode,
|
||||
[CUSTOM_NOTE_NODE]: CustomNoteNode,
|
||||
@ -133,6 +138,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
children,
|
||||
onWorkflowDataUpdate,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowContainerRef = useRef<HTMLDivElement>(null)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const reactflow = useReactFlow()
|
||||
@ -396,7 +402,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
<SyncingDataModal />
|
||||
<CandidateNode />
|
||||
<div
|
||||
className="pointer-events-none absolute left-0 top-0 z-10 flex w-12 items-center justify-center p-1 pl-2"
|
||||
className="pointer-events-none absolute top-0 left-0 z-10 flex w-12 items-center justify-center p-1 pl-2"
|
||||
style={{ height: controlHeight }}
|
||||
>
|
||||
<Control />
|
||||
@ -407,17 +413,26 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
<EdgeContextmenu />
|
||||
<SelectionContextmenu />
|
||||
<HelpLine />
|
||||
{
|
||||
!!showConfirm && (
|
||||
<Confirm
|
||||
isShow
|
||||
onCancel={() => setShowConfirm(undefined)}
|
||||
onConfirm={showConfirm.onConfirm}
|
||||
title={showConfirm.title}
|
||||
content={showConfirm.desc}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && setShowConfirm(undefined)}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{showConfirm?.title}
|
||||
</AlertDialogTitle>
|
||||
{showConfirm?.desc && (
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{showConfirm.desc}
|
||||
</AlertDialogDescription>
|
||||
)}
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={showConfirm?.onConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{children}
|
||||
<ReactFlow
|
||||
nodeTypes={nodeTypes}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import RemoveEffectVarConfirm from '../remove-effect-var-confirm'
|
||||
|
||||
describe('RemoveEffectVarConfirm', () => {
|
||||
it('should render title and content when open', () => {
|
||||
render(
|
||||
<RemoveEffectVarConfirm
|
||||
isShow
|
||||
onConfirm={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.common.effectVarConfirm.title')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.common.effectVarConfirm.content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onConfirm when confirm is clicked', () => {
|
||||
const onConfirm = vi.fn()
|
||||
|
||||
render(
|
||||
<RemoveEffectVarConfirm
|
||||
isShow
|
||||
onConfirm={onConfirm}
|
||||
onCancel={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onCancel when cancel is clicked', async () => {
|
||||
const onCancel = vi.fn()
|
||||
|
||||
render(
|
||||
<RemoveEffectVarConfirm
|
||||
isShow
|
||||
onConfirm={vi.fn()}
|
||||
onCancel={onCancel}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onCancel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2,7 +2,15 @@
|
||||
import type { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
|
||||
type Props = {
|
||||
isShow: boolean
|
||||
@ -17,15 +25,30 @@ const RemoveVarConfirm: FC<Props> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const title = t(`${i18nPrefix}.title`, { ns: 'workflow' })
|
||||
const content = t(`${i18nPrefix}.content`, { ns: 'workflow' })
|
||||
|
||||
return (
|
||||
<Confirm
|
||||
isShow={isShow}
|
||||
title={t(`${i18nPrefix}.title`, { ns: 'workflow' })}
|
||||
content={t(`${i18nPrefix}.content`, { ns: 'workflow' })}
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
<AlertDialog open={isShow} onOpenChange={open => !open && onCancel()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{title}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{content}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(RemoveVarConfirm)
|
||||
|
||||
@ -8,7 +8,6 @@ This document tracks the migration away from legacy overlay APIs.
|
||||
- `@/app/components/base/portal-to-follow-elem`
|
||||
- `@/app/components/base/tooltip`
|
||||
- `@/app/components/base/modal`
|
||||
- `@/app/components/base/confirm`
|
||||
- `@/app/components/base/select` (including `custom` / `pure`)
|
||||
- `@/app/components/base/popover`
|
||||
- `@/app/components/base/dropdown`
|
||||
|
||||
@ -132,7 +132,10 @@
|
||||
},
|
||||
"app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"perfectionist/sort-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx": {
|
||||
@ -281,7 +284,7 @@
|
||||
},
|
||||
"app/components/app-sidebar/dataset-info/dropdown.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -334,14 +337,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/batch-action.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 7
|
||||
@ -364,11 +359,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/clear-all-annotations-confirm-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@ -381,9 +371,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/edit-annotation-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
}
|
||||
@ -423,11 +410,6 @@
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/remove-annotation-confirm-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/type.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 2
|
||||
@ -437,9 +419,6 @@
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 5
|
||||
},
|
||||
@ -469,9 +448,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/app/app-publisher/features-wrapper.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
@ -586,9 +562,6 @@
|
||||
},
|
||||
"app/components/app/configuration/config-var/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -699,7 +672,7 @@
|
||||
},
|
||||
"app/components/app/configuration/config/automatic/get-automatic-res.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 4
|
||||
@ -762,7 +735,7 @@
|
||||
},
|
||||
"app/components/app/configuration/config/code-generator/get-code-generator-res.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 4
|
||||
@ -1085,7 +1058,10 @@
|
||||
},
|
||||
"app/components/app/switch-app-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"perfectionist/sort-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -1387,21 +1363,12 @@
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat-with-history/header-in-mobile.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat-with-history/header/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
@ -1448,7 +1415,7 @@
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat-with-history/sidebar/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"perfectionist/sort-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -1683,19 +1650,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/confirm/index.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 4
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/confirm/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/content-dialog/index.stories.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -3296,7 +3250,7 @@
|
||||
},
|
||||
"app/components/base/tag-management/tag-item-editor.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
@ -3709,7 +3663,7 @@
|
||||
},
|
||||
"app/components/datasets/create-from-pipeline/list/template-card/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create-from-pipeline/list/template-card/operations.tsx": {
|
||||
@ -3986,10 +3940,7 @@
|
||||
},
|
||||
"app/components/datasets/documents/components/operations.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 3
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/components/rename-modal.tsx": {
|
||||
@ -4200,7 +4151,7 @@
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/common/batch-action.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"perfectionist/sort-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
@ -4293,10 +4244,7 @@
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/segment-card/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/segment-detail.tsx": {
|
||||
@ -4426,20 +4374,12 @@
|
||||
},
|
||||
"app/components/datasets/external-api/external-api-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/external-api/external-knowledge-api-card/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -4586,11 +4526,6 @@
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"app/components/datasets/list/dataset-card/components/dataset-card-modals.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/list/dataset-card/components/description.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
@ -4695,7 +4630,10 @@
|
||||
},
|
||||
"app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"perfectionist/sort-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/metadata/metadata-dataset/field.tsx": {
|
||||
@ -4864,7 +4802,10 @@
|
||||
},
|
||||
"app/components/develop/secret-key/secret-key-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"perfectionist/sort-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/develop/tag.tsx": {
|
||||
@ -5094,11 +5035,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/api-based-extension-page/item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/api-based-extension-page/modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -5118,12 +5054,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/data-source-page-new/card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@ -5298,7 +5228,7 @@
|
||||
},
|
||||
"app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -5502,7 +5432,7 @@
|
||||
},
|
||||
"app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -5821,7 +5751,7 @@
|
||||
},
|
||||
"app/components/plugins/plugin-auth/authorized/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -5954,10 +5884,7 @@
|
||||
},
|
||||
"app/components/plugins/plugin-detail-panel/endpoint-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 5
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -6075,11 +6002,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-detail-panel/subscription-list/delete-confirm.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@ -6212,7 +6134,7 @@
|
||||
},
|
||||
"app/components/plugins/plugin-item/action.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-item/index.tsx": {
|
||||
@ -6392,11 +6314,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/rag-pipeline/components/conversion.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx": {
|
||||
"react/component-hook-factories": {
|
||||
"count": 1
|
||||
@ -6542,11 +6459,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
@ -6760,7 +6672,7 @@
|
||||
},
|
||||
"app/components/tools/mcp/detail/content.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@ -6807,7 +6719,7 @@
|
||||
},
|
||||
"app/components/tools/mcp/mcp-service-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/tools/mcp/modal.tsx": {
|
||||
@ -6816,15 +6728,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/tools/mcp/provider-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 7
|
||||
},
|
||||
"tailwindcss/no-unnecessary-whitespace": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
@ -6852,11 +6755,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/tools/provider/detail.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/tools/provider/empty.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
@ -6914,11 +6812,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow-app/components/workflow-onboarding-modal/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
@ -7371,9 +7264,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@ -7724,11 +7614,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/remove-effect-var-confirm.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
@ -10363,9 +10248,6 @@
|
||||
}
|
||||
},
|
||||
"hooks/use-pay.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 4
|
||||
}
|
||||
|
||||
@ -52,13 +52,6 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [
|
||||
],
|
||||
message: 'Deprecated: use @/app/components/base/ui/select instead. See issue #32767.',
|
||||
},
|
||||
{
|
||||
group: [
|
||||
'**/base/confirm',
|
||||
'**/base/confirm/index',
|
||||
],
|
||||
message: 'Deprecated: use @/app/components/base/ui/alert-dialog instead. See issue #32767.',
|
||||
},
|
||||
{
|
||||
group: [
|
||||
'**/base/popover',
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
'use client'
|
||||
|
||||
import type { IConfirm } from '@/app/components/base/confirm'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import { useRouter, useSearchParams } from '@/next/navigation'
|
||||
import { useNotionBinding } from '@/service/use-common'
|
||||
|
||||
type ConfirmType = Pick<IConfirm, 'type' | 'title' | 'content'>
|
||||
type ConfirmType = {
|
||||
type: 'info' | 'warning'
|
||||
title: string
|
||||
}
|
||||
|
||||
const useAnthropicCheckPay = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -96,16 +105,32 @@ export const CheckModal = () => {
|
||||
if (!confirmInfo || !showPayStatusModal)
|
||||
return null
|
||||
|
||||
const description = (confirmInfo as { desc?: string }).desc || ''
|
||||
|
||||
return (
|
||||
<Confirm
|
||||
isShow
|
||||
onCancel={handleCancelShowPayStatusModal}
|
||||
onConfirm={handleCancelShowPayStatusModal}
|
||||
showCancel={false}
|
||||
type={confirmInfo.type === 'info' ? 'info' : 'warning'}
|
||||
title={confirmInfo.title}
|
||||
content={(confirmInfo as unknown as { desc: string }).desc || ''}
|
||||
confirmText={(confirmInfo.type === 'info' && t('operation.ok', { ns: 'common' })) || ''}
|
||||
/>
|
||||
<AlertDialog open={showPayStatusModal} onOpenChange={open => !open && handleCancelShowPayStatusModal()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
{confirmInfo.title}
|
||||
</AlertDialogTitle>
|
||||
{description && (
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{description}
|
||||
</AlertDialogDescription>
|
||||
)}
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogConfirmButton
|
||||
tone={confirmInfo.type !== 'info' ? 'destructive' : 'default'}
|
||||
onClick={handleCancelShowPayStatusModal}
|
||||
>
|
||||
{confirmInfo.type === 'info'
|
||||
? t('operation.ok', { ns: 'common' })
|
||||
: t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user