mirror of
https://github.com/langgenius/dify.git
synced 2026-04-24 00:59:19 +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', () => ({
|
vi.mock('@/app/components/workflow/dsl-export-confirm-modal', () => ({
|
||||||
default: ({ onConfirm, onClose }: Record<string, unknown>) => (
|
default: ({ onConfirm, onClose }: Record<string, unknown>) => (
|
||||||
<div data-testid="dsl-export-confirm-modal">
|
<div data-testid="dsl-export-confirm-modal">
|
||||||
@ -342,14 +328,16 @@ describe('App Card Operations Flow', () => {
|
|||||||
fireEvent.click(deleteBtn)
|
fireEvent.click(deleteBtn)
|
||||||
})
|
})
|
||||||
|
|
||||||
const confirmBtn = screen.queryByTestId('confirm-delete')
|
await waitFor(() => {
|
||||||
if (confirmBtn) {
|
expect(screen.getByText('app.deleteAppConfirmTitle')).toBeInTheDocument()
|
||||||
fireEvent.click(confirmBtn)
|
})
|
||||||
|
|
||||||
await waitFor(() => {
|
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Deletable App' } })
|
||||||
expect(mockDeleteAppMutation).toHaveBeenCalledWith('app-to-delete')
|
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', () => ({
|
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||||
default: { notify: vi.fn() },
|
default: { notify: vi.fn() },
|
||||||
toast: {
|
toast: {
|
||||||
@ -242,6 +222,8 @@ vi.mock('@/app/components/tools/provider/tool-item', () => ({
|
|||||||
|
|
||||||
const { default: ProviderDetail } = await import('@/app/components/tools/provider/detail')
|
const { default: ProviderDetail } = await import('@/app/components/tools/provider/detail')
|
||||||
|
|
||||||
|
const getDeleteConfirmButton = () => screen.getByRole('button', { name: /operation\.confirm$/ })
|
||||||
|
|
||||||
const makeCollection = (overrides: Partial<Collection> = {}): Collection => ({
|
const makeCollection = (overrides: Partial<Collection> = {}): Collection => ({
|
||||||
id: 'test-collection',
|
id: 'test-collection',
|
||||||
name: 'test_collection',
|
name: 'test_collection',
|
||||||
@ -465,11 +447,10 @@ describe('Tool Provider Detail Flow Integration', () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByTestId('custom-modal-remove'))
|
fireEvent.click(screen.getByTestId('custom-modal-remove'))
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
|
||||||
expect(screen.getByText('Delete Tool')).toBeInTheDocument()
|
expect(screen.getByText('Delete Tool')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockRemoveCustomCollection).toHaveBeenCalledWith('test_collection')
|
expect(mockRemoveCustomCollection).toHaveBeenCalledWith('test_collection')
|
||||||
expect(mockOnRefreshData).toHaveBeenCalled()
|
expect(mockOnRefreshData).toHaveBeenCalled()
|
||||||
@ -527,10 +508,10 @@ describe('Tool Provider Detail Flow Integration', () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByTestId('wf-modal-remove'))
|
fireEvent.click(screen.getByTestId('wf-modal-remove'))
|
||||||
await waitFor(() => {
|
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(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeleteWorkflowTool).toHaveBeenCalledWith('test-collection')
|
expect(mockDeleteWorkflowTool).toHaveBeenCalledWith('test-collection')
|
||||||
expect(mockOnRefreshData).toHaveBeenCalled()
|
expect(mockOnRefreshData).toHaveBeenCalled()
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { useBoolean } from 'ahooks'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
|
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||||
@ -14,6 +13,15 @@ import {
|
|||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
import { Button } from '@/app/components/base/ui/button'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps'
|
import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps'
|
||||||
import { docURL } from './config'
|
import { docURL } from './config'
|
||||||
@ -679,14 +687,24 @@ const ProviderConfigModal: FC<Props> = ({
|
|||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<Confirm
|
<AlertDialog open onOpenChange={open => !open && hideRemoveConfirm()}>
|
||||||
isShow
|
<AlertDialogContent>
|
||||||
type="warning"
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
title={t(`${I18N_PREFIX}.removeConfirmTitle`, { ns: 'app', key: t(`tracing.${type}.title`, { ns: 'app' }) })!}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
content={t(`${I18N_PREFIX}.removeConfirmContent`, { ns: 'app' })}
|
{t(`${I18N_PREFIX}.removeConfirmTitle`, { ns: 'app', key: t(`tracing.${type}.title`, { ns: 'app' }) })!}
|
||||||
onConfirm={handleRemove}
|
</AlertDialogTitle>
|
||||||
onCancel={hideRemoveConfirm}
|
<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 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 userEvent from '@testing-library/user-event'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { AppModeEnum } from '@/types/app'
|
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', () => ({
|
vi.mock('@/app/components/workflow/update-dsl-modal', () => ({
|
||||||
default: ({ onCancel, onBackup }: { onCancel: () => void, onBackup: () => void }) => (
|
default: ({ onCancel, onBackup }: { onCancel: () => void, onBackup: () => void }) => (
|
||||||
<div data-testid="import-dsl-modal">
|
<div data-testid="import-dsl-modal">
|
||||||
@ -113,7 +95,7 @@ describe('AppInfoModals', () => {
|
|||||||
render(<AppInfoModals {...defaultProps} activeModal={null} />)
|
render(<AppInfoModals {...defaultProps} activeModal={null} />)
|
||||||
})
|
})
|
||||||
expect(screen.queryByTestId('switch-modal')).not.toBeInTheDocument()
|
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 () => {
|
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 () => {
|
await act(async () => {
|
||||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||||
})
|
})
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const confirm = screen.getByTestId('confirm-modal')
|
expect(screen.getByText('app.deleteAppConfirmTitle')).toBeInTheDocument()
|
||||||
expect(confirm).toBeInTheDocument()
|
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
||||||
expect(confirm).toHaveAttribute('data-title', 'app.deleteAppConfirmTitle')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -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 () => {
|
await act(async () => {
|
||||||
render(<AppInfoModals {...defaultProps} activeModal="exportWarning" />)
|
render(<AppInfoModals {...defaultProps} activeModal="exportWarning" />)
|
||||||
})
|
})
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const confirm = screen.getByTestId('confirm-modal')
|
expect(screen.getByText('workflow.sidebar.exportWarning')).toBeInTheDocument()
|
||||||
expect(confirm).toBeInTheDocument()
|
|
||||||
expect(confirm).toHaveAttribute('data-title', 'workflow.sidebar.exportWarning')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -202,20 +181,47 @@ describe('AppInfoModals', () => {
|
|||||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(() => expect(screen.getByText('Cancel')).toBeInTheDocument())
|
await waitFor(() => expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeInTheDocument())
|
||||||
await user.click(screen.getByText('Cancel'))
|
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||||
|
|
||||||
expect(defaultProps.closeModal).toHaveBeenCalledTimes(1)
|
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 () => {
|
it('should call onConfirmDelete when confirm on delete modal', async () => {
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
render(<AppInfoModals {...defaultProps} activeModal="delete" />)
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(() => expect(screen.getByText('Confirm')).toBeInTheDocument())
|
await user.type(screen.getByRole('textbox'), 'Test App')
|
||||||
await user.click(screen.getByText('Confirm'))
|
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||||
|
|
||||||
expect(defaultProps.onConfirmDelete).toHaveBeenCalledTimes(1)
|
expect(defaultProps.onConfirmDelete).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
@ -226,8 +232,8 @@ describe('AppInfoModals', () => {
|
|||||||
render(<AppInfoModals {...defaultProps} activeModal="exportWarning" />)
|
render(<AppInfoModals {...defaultProps} activeModal="exportWarning" />)
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(() => expect(screen.getByText('Confirm')).toBeInTheDocument())
|
await waitFor(() => expect(screen.getByRole('button', { name: 'common.operation.confirm' })).toBeInTheDocument())
|
||||||
await user.click(screen.getByText('Confirm'))
|
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||||
|
|
||||||
expect(defaultProps.handleConfirmExport).toHaveBeenCalledTimes(1)
|
expect(defaultProps.handleConfirmExport).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,12 +6,21 @@ import type { App, AppSSO } from '@/types/app'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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'
|
import dynamic from '@/next/dynamic'
|
||||||
|
|
||||||
const SwitchAppModal = dynamic(() => import('@/app/components/app/switch-app-modal'), { ssr: false })
|
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 CreateAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), { ssr: false })
|
||||||
const DuplicateAppModal = dynamic(() => import('@/app/components/app/duplicate-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 UpdateDSLModal = dynamic(() => import('@/app/components/workflow/update-dsl-modal'), { ssr: false })
|
||||||
const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/dsl-export-confirm-modal'), { ssr: false })
|
const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/dsl-export-confirm-modal'), { ssr: false })
|
||||||
|
|
||||||
@ -44,6 +53,12 @@ const AppInfoModals = ({
|
|||||||
}: AppInfoModalsProps) => {
|
}: AppInfoModalsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [confirmDeleteInput, setConfirmDeleteInput] = useState('')
|
const [confirmDeleteInput, setConfirmDeleteInput] = useState('')
|
||||||
|
const isDeleteConfirmDisabled = confirmDeleteInput !== appDetail.name
|
||||||
|
|
||||||
|
const handleDeleteDialogClose = () => {
|
||||||
|
setConfirmDeleteInput('')
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -85,39 +100,73 @@ const AppInfoModals = ({
|
|||||||
onHide={closeModal}
|
onHide={closeModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeModal === 'delete' && (
|
<AlertDialog open={activeModal === 'delete'} onOpenChange={open => !open && handleDeleteDialogClose()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('deleteAppConfirmTitle', { ns: 'app' })}
|
<form
|
||||||
content={t('deleteAppConfirmContent', { ns: 'app' })}
|
className="flex flex-col"
|
||||||
isShow
|
onSubmit={(e) => {
|
||||||
confirmInputLabel={t('deleteAppConfirmInputLabel', { ns: 'app', appName: appDetail.name })}
|
e.preventDefault()
|
||||||
confirmInputPlaceholder={t('deleteAppConfirmInputPlaceholder', { ns: 'app' })}
|
if (isDeleteConfirmDisabled)
|
||||||
confirmInputValue={confirmDeleteInput}
|
return
|
||||||
onConfirmInputChange={setConfirmDeleteInput}
|
onConfirmDelete()
|
||||||
confirmInputMatchValue={appDetail.name}
|
}}
|
||||||
onConfirm={onConfirmDelete}
|
>
|
||||||
onCancel={() => {
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
setConfirmDeleteInput('')
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
closeModal()
|
{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' && (
|
{activeModal === 'importDSL' && (
|
||||||
<UpdateDSLModal
|
<UpdateDSLModal
|
||||||
onCancel={closeModal}
|
onCancel={closeModal}
|
||||||
onBackup={exportCheck}
|
onBackup={exportCheck}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeModal === 'exportWarning' && (
|
<AlertDialog open={activeModal === 'exportWarning'} onOpenChange={open => !open && closeModal()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
type="info"
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('sidebar.exportWarning', { ns: 'workflow' })}
|
{t('sidebar.exportWarning', { ns: 'workflow' })}
|
||||||
content={t('sidebar.exportWarningDesc', { ns: 'workflow' })}
|
</AlertDialogTitle>
|
||||||
onConfirm={handleConfirmExport}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
onCancel={closeModal}
|
{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 && (
|
{secretEnvList.length > 0 && (
|
||||||
<DSLExportConfirmModal
|
<DSLExportConfirmModal
|
||||||
envList={secretEnvList}
|
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', () => ({
|
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||||
PortalToFollowElem: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
PortalToFollowElem: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick?: () => void }) => (
|
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 user.click(screen.getByText('common.operation.delete'))
|
||||||
|
|
||||||
await waitFor(() => {
|
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(() => {
|
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(() => {
|
await waitFor(() => {
|
||||||
expect(mockToast).toHaveBeenCalledWith('check failed', { type: 'error' })
|
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 { cn } from '@/utils/classnames'
|
||||||
import { downloadBlob } from '@/utils/download'
|
import { downloadBlob } from '@/utils/download'
|
||||||
import ActionButton from '../../base/action-button'
|
import ActionButton from '../../base/action-button'
|
||||||
import Confirm from '../../base/confirm'
|
|
||||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
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 RenameDatasetModal from '../../datasets/rename-modal'
|
||||||
import Menu from './menu'
|
import Menu from './menu'
|
||||||
|
|
||||||
@ -135,15 +143,26 @@ const DropDown = ({
|
|||||||
onSuccess={refreshDataset}
|
onSuccess={refreshDataset}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showConfirmDelete && (
|
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && setShowConfirmDelete(false)}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={confirmMessage}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow={showConfirmDelete}
|
{t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||||
onConfirm={onConfirmDelete}
|
</AlertDialogTitle>
|
||||||
onCancel={() => setShowConfirmDelete(false)}
|
<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>
|
</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 * as React from 'react'
|
||||||
import BatchAction from '../batch-action'
|
import BatchAction from '../batch-action'
|
||||||
|
|
||||||
@ -29,14 +29,28 @@ describe('BatchAction', () => {
|
|||||||
render(<BatchAction {...baseProps} onBatchDelete={onBatchDelete} />)
|
render(<BatchAction {...baseProps} onBatchDelete={onBatchDelete} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.delete' }))
|
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(within(dialog).getByRole('button', { name: 'common.operation.delete' }))
|
||||||
fireEvent.click(screen.getAllByRole('button', { name: 'common.operation.delete' })[1])
|
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(onBatchDelete).toHaveBeenCalledTimes(1)
|
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 { useBoolean } from 'ahooks'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Divider from '@/app/components/base/divider'
|
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'
|
import { cn } from '@/utils/classnames'
|
||||||
|
|
||||||
const i18nPrefix = 'batchAction'
|
const i18nPrefix = 'batchAction'
|
||||||
@ -41,38 +48,42 @@ const BatchAction: FC<IBatchActionProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none flex w-full justify-center', className)}>
|
<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="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">
|
<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}
|
{selectedIds.length}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
<Divider type="vertical" className="mx-0.5 h-3.5 bg-divider-regular" />
|
<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}>
|
<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" />
|
<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' })}
|
{t('operation.delete', { ns: 'common' })}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider type="vertical" className="mx-0.5 h-3.5 bg-divider-regular" />
|
<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' })}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{
|
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||||
isShowDeleteConfirm && (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('list.delete.title', { ns: 'appAnnotation' })}
|
{t('list.delete.title', { ns: 'appAnnotation' })}
|
||||||
confirmText={t('operation.delete', { ns: 'common' })}
|
</AlertDialogTitle>
|
||||||
onConfirm={handleBatchDelete}
|
</div>
|
||||||
onCancel={hideDeleteConfirm}
|
<AlertDialogActions>
|
||||||
isLoading={isDeleting}
|
<AlertDialogCancelButton>
|
||||||
isDisabled={isDeleting}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
/>
|
</AlertDialogCancelButton>
|
||||||
)
|
<AlertDialogConfirmButton loading={isDeleting} disabled={isDeleting} onClick={handleBatchDelete}>
|
||||||
}
|
{t('operation.delete', { ns: 'common' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,14 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 = {
|
type Props = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@ -17,15 +24,26 @@ const ClearAllAnnotationsConfirmModal: FC<Props> = ({
|
|||||||
onConfirm,
|
onConfirm,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const title = t('table.header.clearAllConfirm', { ns: 'appAnnotation' })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Confirm
|
<AlertDialog open={isShow} onOpenChange={open => !open && onHide()}>
|
||||||
isShow={isShow}
|
<AlertDialogContent>
|
||||||
onCancel={onHide}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onConfirm={onConfirm}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
type="danger"
|
{title}
|
||||||
title={t('table.header.clearAllConfirm', { ns: 'appAnnotation' })}
|
</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
|
// Assert
|
||||||
expect(mockOnRemove).toHaveBeenCalled()
|
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)
|
// Edge Cases (REQUIRED)
|
||||||
|
|||||||
@ -3,9 +3,16 @@ import type { FC } from 'react'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Drawer from '@/app/components/base/drawer-plus'
|
import Drawer from '@/app/components/base/drawer-plus'
|
||||||
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import AnnotationFull from '@/app/components/billing/annotation-full'
|
import AnnotationFull from '@/app/components/billing/annotation-full'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
@ -106,16 +113,33 @@ const EditAnnotationModal: FC<Props> = ({
|
|||||||
readonly={isAdd && isAnnotationFull}
|
readonly={isAdd && isAnnotationFull}
|
||||||
onSave={editedContent => handleSave(EditItemType.Answer, editedContent)}
|
onSave={editedContent => handleSave(EditItemType.Answer, editedContent)}
|
||||||
/>
|
/>
|
||||||
<Confirm
|
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||||
isShow={showModal}
|
<AlertDialogContent>
|
||||||
onCancel={() => setShowModal(false)}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onConfirm={() => {
|
<AlertDialogTitle
|
||||||
onRemove()
|
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||||
setShowModal(false)
|
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||||
onHide()
|
>
|
||||||
}}
|
{t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||||
title={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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,7 +2,14 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 = {
|
type Props = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@ -16,14 +23,26 @@ const RemoveAnnotationConfirmModal: FC<Props> = ({
|
|||||||
onRemove,
|
onRemove,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const title = t('feature.annotation.removeConfirm', { ns: 'appDebug' })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Confirm
|
<AlertDialog open={isShow} onOpenChange={open => !open && onHide()}>
|
||||||
isShow={isShow}
|
<AlertDialogContent>
|
||||||
onCancel={onHide}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onConfirm={onRemove}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
{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)
|
export default React.memo(RemoveAnnotationConfirmModal)
|
||||||
|
|||||||
@ -157,4 +157,17 @@ describe('ViewAnnotationModal', () => {
|
|||||||
expect(props.onHide).toHaveBeenCalledTimes(1)
|
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 { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Badge from '@/app/components/base/badge'
|
import Badge from '@/app/components/base/badge'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Drawer from '@/app/components/base/drawer-plus'
|
import Drawer from '@/app/components/base/drawer-plus'
|
||||||
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
|
import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
|
||||||
import Pagination from '@/app/components/base/pagination'
|
import Pagination from '@/app/components/base/pagination'
|
||||||
import TabSlider from '@/app/components/base/tab-slider-plain'
|
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 { APP_PAGE_LIMIT } from '@/config'
|
||||||
import useTimestamp from '@/hooks/use-timestamp'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import { fetchHitHistoryList } from '@/service/annotation'
|
import { fetchHitHistoryList } from '@/service/annotation'
|
||||||
@ -212,16 +219,33 @@ const ViewAnnotationModal: FC<Props> = ({
|
|||||||
<div className="space-y-6 p-6 pb-4">
|
<div className="space-y-6 p-6 pb-4">
|
||||||
{activeTab === TabType.annotation ? annotationTab : hitHistoryTab}
|
{activeTab === TabType.annotation ? annotationTab : hitHistoryTab}
|
||||||
</div>
|
</div>
|
||||||
<Confirm
|
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||||
isShow={showModal}
|
<AlertDialogContent>
|
||||||
onCancel={() => setShowModal(false)}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onConfirm={async () => {
|
<AlertDialogTitle
|
||||||
await onRemove()
|
title={t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||||
setShowModal(false)
|
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||||
onHide()
|
>
|
||||||
}}
|
{t('feature.annotation.removeConfirm', { ns: 'appDebug' })}
|
||||||
title={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>
|
</div>
|
||||||
)}
|
)}
|
||||||
foot={id
|
foot={id
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable ts/no-explicit-any */
|
/* 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'
|
import FeaturesWrappedAppPublisher from '../features-wrapper'
|
||||||
|
|
||||||
const mockSetFeatures = vi.fn()
|
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 { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AppPublisher from '@/app/components/app/app-publisher'
|
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 { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
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 { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||||
import { Resolution } from '@/types/app'
|
import { Resolution } from '@/types/app'
|
||||||
|
|
||||||
@ -74,15 +82,24 @@ const FeaturesWrappedAppPublisher = (props: Props) => {
|
|||||||
onRestore: () => setRestoreConfirmOpen(true),
|
onRestore: () => setRestoreConfirmOpen(true),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{restoreConfirmOpen && (
|
<AlertDialog open={restoreConfirmOpen} onOpenChange={open => !open && setRestoreConfirmOpen(false)}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('resetConfig.title', { ns: 'appDebug' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('resetConfig.message', { ns: 'appDebug' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow={restoreConfirmOpen}
|
{t('resetConfig.title', { ns: 'appDebug' })}
|
||||||
onConfirm={handleConfirm}
|
</AlertDialogTitle>
|
||||||
onCancel={() => setRestoreConfirmOpen(false)}
|
<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 { useTranslation } from 'react-i18next'
|
||||||
import { ReactSortable } from 'react-sortablejs'
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
import ConfigContext from '@/context/debug-configuration'
|
import ConfigContext from '@/context/debug-configuration'
|
||||||
@ -264,7 +272,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
>
|
>
|
||||||
{!hasVar && (
|
{!hasVar && (
|
||||||
<div className="mt-1 px-3 pb-3">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasVar && (
|
{hasVar && (
|
||||||
@ -313,18 +321,29 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isShowDeleteContextVarModal && (
|
<AlertDialog open={isShowDeleteContextVarModal} onOpenChange={open => !open && hideDeleteContextVarModal()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
isShow={isShowDeleteContextVarModal}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
title={t('feature.dataSet.queryVariable.deleteContextVarTitle', { ns: 'appDebug', varName: promptVariables[removeIndex as number]?.name })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
content={t('feature.dataSet.queryVariable.deleteContextVarTip', { ns: 'appDebug' })}
|
{t('feature.dataSet.queryVariable.deleteContextVarTitle', { ns: 'appDebug', varName: promptVariables[removeIndex as number]?.name })}
|
||||||
onConfirm={() => {
|
</AlertDialogTitle>
|
||||||
didRemoveVar(removeIndex as number)
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
hideDeleteContextVarModal()
|
{t('feature.dataSet.queryVariable.deleteContextVarTip', { ns: 'appDebug' })}
|
||||||
}}
|
</AlertDialogDescription>
|
||||||
onCancel={hideDeleteContextVarModal}
|
</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>
|
</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 { AppModeEnum } from '@/types/app'
|
||||||
import GetAutomaticRes from '../get-automatic-res'
|
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 () => {
|
it('should request workflow generation and surface service errors', async () => {
|
||||||
mockGenerateRule.mockResolvedValue({
|
mockGenerateRule.mockResolvedValue({
|
||||||
error: 'generation failed',
|
error: 'generation failed',
|
||||||
|
|||||||
@ -19,12 +19,19 @@ import { useBoolean, useSessionStorageState } from 'ahooks'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
|
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
|
|
||||||
@ -387,18 +394,29 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
|||||||
)}
|
)}
|
||||||
{isLoading && renderLoading}
|
{isLoading && renderLoading}
|
||||||
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
|
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
|
||||||
{isShowConfirmOverwrite && (
|
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('generate.overwriteTitle', { ns: 'appDebug' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('generate.overwriteMessage', { ns: 'appDebug' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow
|
{t('generate.overwriteTitle', { ns: 'appDebug' })}
|
||||||
onConfirm={() => {
|
</AlertDialogTitle>
|
||||||
hideShowConfirmOverwrite()
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
onFinished(current!)
|
{t('generate.overwriteMessage', { ns: 'appDebug' })}
|
||||||
}}
|
</AlertDialogDescription>
|
||||||
onCancel={hideShowConfirmOverwrite}
|
</div>
|
||||||
/>
|
<AlertDialogActions>
|
||||||
)}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
|
<AlertDialogConfirmButton
|
||||||
|
onClick={() => {
|
||||||
|
hideShowConfirmOverwrite()
|
||||||
|
onFinished(current!)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</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 { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
import GetCodeGeneratorResModal from '../get-code-generator-res'
|
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 () => {
|
it('should surface service errors without creating a result version', async () => {
|
||||||
mockGenerateRule.mockResolvedValue({
|
mockGenerateRule.mockResolvedValue({
|
||||||
error: 'generation failed',
|
error: 'generation failed',
|
||||||
|
|||||||
@ -10,10 +10,18 @@ import {
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
@ -263,18 +271,29 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isShowConfirmOverwrite && (
|
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('codegen.overwriteConfirmMessage', { ns: 'appDebug' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow
|
{t('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
||||||
onConfirm={() => {
|
</AlertDialogTitle>
|
||||||
hideShowConfirmOverwrite()
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
onFinished(current!)
|
{t('codegen.overwriteConfirmMessage', { ns: 'appDebug' })}
|
||||||
}}
|
</AlertDialogDescription>
|
||||||
onCancel={hideShowConfirmOverwrite}
|
</div>
|
||||||
/>
|
<AlertDialogActions>
|
||||||
)}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
|
<AlertDialogConfirmButton
|
||||||
|
onClick={() => {
|
||||||
|
hideShowConfirmOverwrite()
|
||||||
|
onFinished(current!)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,28 +153,26 @@ const ConfigurationView: FC<ConfigurationViewModel> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showUseGPT4Confirm && (
|
<AlertDialog open={showUseGPT4Confirm} onOpenChange={open => !open && setShowUseGPT4Confirm(false)}>
|
||||||
<AlertDialog open={showUseGPT4Confirm} onOpenChange={open => !open && setShowUseGPT4Confirm(false)}>
|
<AlertDialogContent>
|
||||||
<AlertDialogContent>
|
<div className="flex flex-col items-start gap-2 self-stretch px-6 pt-6 pb-4">
|
||||||
<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">
|
||||||
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
{t('trailUseGPT4Info.title', { ns: 'appDebug' })}
|
||||||
{t('trailUseGPT4Info.title', { ns: 'appDebug' })}
|
</AlertDialogTitle>
|
||||||
</AlertDialogTitle>
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
{t('trailUseGPT4Info.description', { ns: 'appDebug' })}
|
||||||
{t('trailUseGPT4Info.description', { ns: 'appDebug' })}
|
</AlertDialogDescription>
|
||||||
</AlertDialogDescription>
|
</div>
|
||||||
</div>
|
<AlertDialogActions>
|
||||||
<AlertDialogActions>
|
<AlertDialogCancelButton tone="default">
|
||||||
<AlertDialogCancelButton tone="default">
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
</AlertDialogCancelButton>
|
||||||
</AlertDialogCancelButton>
|
<AlertDialogConfirmButton variant="primary" tone="default" onClick={onConfirmUseGPT4}>
|
||||||
<AlertDialogConfirmButton variant="primary" tone="default" onClick={onConfirmUseGPT4}>
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
{t('operation.confirm', { ns: 'common' })}
|
</AlertDialogConfirmButton>
|
||||||
</AlertDialogConfirmButton>
|
</AlertDialogActions>
|
||||||
</AlertDialogActions>
|
</AlertDialogContent>
|
||||||
</AlertDialogContent>
|
</AlertDialog>
|
||||||
</AlertDialog>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isShowSelectDataSet && (
|
{isShowSelectDataSet && (
|
||||||
<SelectDataSet
|
<SelectDataSet
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { AppDetailResponse } from '@/models/app'
|
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 { AccessMode } from '@/models/access-control'
|
||||||
import { AppModeEnum } from '@/types/app'
|
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', () => {
|
describe('app-card-sections', () => {
|
||||||
const t = (key: string) => key
|
const t = (key: string) => key
|
||||||
@ -100,4 +100,31 @@ describe('app-card-sections', () => {
|
|||||||
expect(screen.getByText('overview.appInfo.customize.entry')).toBeInTheDocument()
|
expect(screen.getByText('overview.appInfo.customize.entry')).toBeInTheDocument()
|
||||||
expect(AppModeEnum.CHAT).toBe('chat')
|
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!" />
|
<CopyFeedback content={accessibleUrl} className="size-6!" />
|
||||||
{isApp && <ShareQRCode content={accessibleUrl} />}
|
{isApp && <ShareQRCode content={accessibleUrl} />}
|
||||||
{isApp && <Divider type="vertical" className="mx-0.5! h-3.5! shrink-0" />}
|
{isApp && <Divider type="vertical" className="mx-0.5! h-3.5! shrink-0" />}
|
||||||
{showConfirmDelete && (
|
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && onHideRegenerateConfirm()}>
|
||||||
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && onHideRegenerateConfirm()}>
|
<AlertDialogContent>
|
||||||
<AlertDialogContent>
|
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-6 pb-4 pl-6">
|
||||||
<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">
|
||||||
<AlertDialogTitle className="w-full title-2xl-semi-bold text-text-primary">
|
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||||
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
</AlertDialogTitle>
|
||||||
</AlertDialogTitle>
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
{t('overview.appInfo.regenerateNotice', { ns: 'appOverview' })}
|
||||||
{t('overview.appInfo.regenerateNotice', { ns: 'appOverview' })}
|
</AlertDialogDescription>
|
||||||
</AlertDialogDescription>
|
</div>
|
||||||
</div>
|
<AlertDialogActions>
|
||||||
<AlertDialogActions>
|
<AlertDialogCancelButton onClick={onHideRegenerateConfirm}>
|
||||||
<AlertDialogCancelButton onClick={onHideRegenerateConfirm}>
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
</AlertDialogCancelButton>
|
||||||
</AlertDialogCancelButton>
|
<AlertDialogConfirmButton onClick={onRegenerate}>
|
||||||
<AlertDialogConfirmButton onClick={onRegenerate}>
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
{t('operation.confirm', { ns: 'common' })}
|
</AlertDialogConfirmButton>
|
||||||
</AlertDialogConfirmButton>
|
</AlertDialogActions>
|
||||||
</AlertDialogActions>
|
</AlertDialogContent>
|
||||||
</AlertDialogContent>
|
</AlertDialog>
|
||||||
</AlertDialog>
|
|
||||||
)}
|
|
||||||
{isApp && isCurrentWorkspaceManager && (
|
{isApp && isCurrentWorkspaceManager && (
|
||||||
<MaybeTooltip content={t('overview.appInfo.regenerate', { ns: 'appOverview' }) || ''}>
|
<MaybeTooltip content={t('overview.appInfo.regenerate', { ns: 'appOverview' }) || ''}>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -335,6 +335,7 @@ describe('SwitchAppModal', () => {
|
|||||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||||
|
|
||||||
expect(screen.queryByRole('button', { name: 'common.operation.confirm' })).not.toBeInTheDocument()
|
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 () => {
|
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 { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import Checkbox from '@/app/components/base/checkbox'
|
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 { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
import { Button } from '@/app/components/base/ui/button'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
@ -91,6 +99,14 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
|||||||
setShowConfirmDelete(true)
|
setShowConfirmDelete(true)
|
||||||
}, [removeOriginal])
|
}, [removeOriginal])
|
||||||
|
|
||||||
|
const handleConfirmDeleteOpenChange = (open: boolean) => {
|
||||||
|
if (open)
|
||||||
|
return
|
||||||
|
|
||||||
|
setShowConfirmDelete(false)
|
||||||
|
setRemoveOriginal(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@ -156,18 +172,29 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
{showConfirmDelete && (
|
<AlertDialog
|
||||||
<Confirm
|
open={showConfirmDelete}
|
||||||
title={t('deleteAppConfirmTitle', { ns: 'app' })}
|
onOpenChange={handleConfirmDeleteOpenChange}
|
||||||
content={t('deleteAppConfirmContent', { ns: 'app' })}
|
>
|
||||||
isShow={showConfirmDelete}
|
<AlertDialogContent>
|
||||||
onConfirm={() => setShowConfirmDelete(false)}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onCancel={() => {
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
setShowConfirmDelete(false)
|
{t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||||
setRemoveOriginal(false)
|
</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 AppIcon from '@/app/components/base/app-icon'
|
||||||
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
|
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 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 { useChatWithHistoryContext } from './context'
|
||||||
import MobileOperationDropdown from './header/mobile-operation-dropdown'
|
import MobileOperationDropdown from './header/mobile-operation-dropdown'
|
||||||
import Operation from './header/operation'
|
import Operation from './header/operation'
|
||||||
@ -78,7 +86,7 @@ const HeaderInMobile = () => {
|
|||||||
imageUrl={appData?.site.icon_url}
|
imageUrl={appData?.site.icon_url}
|
||||||
background={appData?.site.icon_background}
|
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}
|
{appData?.site.title}
|
||||||
</div>
|
</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 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="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="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>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<InputsFormContent />
|
<InputsFormContent />
|
||||||
@ -129,15 +137,24 @@ const HeaderInMobile = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!showConfirm && (
|
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && handleCancelConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('chat.deleteConversation.title', { ns: 'share' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('chat.deleteConversation.content', { ns: 'share' }) || ''}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow
|
{t('chat.deleteConversation.title', { ns: 'share' })}
|
||||||
onCancel={handleCancelConfirm}
|
</AlertDialogTitle>
|
||||||
onConfirm={handleDelete}
|
<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 && (
|
{showRename && (
|
||||||
<RenameModal
|
<RenameModal
|
||||||
isShow
|
isShow
|
||||||
|
|||||||
@ -10,8 +10,16 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
|
|||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
|
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 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 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 { cn } from '@/utils/classnames'
|
||||||
import {
|
import {
|
||||||
useChatWithHistoryContext,
|
useChatWithHistoryContext,
|
||||||
@ -89,7 +97,7 @@ const Header = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!currentConversationId && (
|
{!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 && (
|
{currentConversationId && currentConversationItem && isSidebarCollapsed && (
|
||||||
<>
|
<>
|
||||||
@ -141,15 +149,24 @@ const Header = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!showConfirm && (
|
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && handleCancelConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('chat.deleteConversation.title', { ns: 'share' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('chat.deleteConversation.content', { ns: 'share' }) || ''}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow
|
{t('chat.deleteConversation.title', { ns: 'share' })}
|
||||||
onCancel={handleCancelConfirm}
|
</AlertDialogTitle>
|
||||||
onConfirm={handleDelete}
|
<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 && (
|
{showRename && (
|
||||||
<RenameModal
|
<RenameModal
|
||||||
isShow
|
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', () => {
|
describe('Sidebar Index', () => {
|
||||||
const mockContextValue = {
|
const mockContextValue = {
|
||||||
isInstalledApp: false,
|
isInstalledApp: false,
|
||||||
@ -475,8 +459,7 @@ describe('Sidebar Index', () => {
|
|||||||
render(<Sidebar />)
|
render(<Sidebar />)
|
||||||
|
|
||||||
await user.click(screen.getByTestId('delete-1'))
|
await user.click(screen.getByTestId('delete-1'))
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
||||||
expect(screen.getByTestId('confirm-title')).toBeInTheDocument()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call handleDeleteConversation when confirm is clicked', async () => {
|
it('should call handleDeleteConversation when confirm is clicked', async () => {
|
||||||
@ -490,7 +473,7 @@ describe('Sidebar Index', () => {
|
|||||||
render(<Sidebar />)
|
render(<Sidebar />)
|
||||||
|
|
||||||
await user.click(screen.getByTestId('delete-1'))
|
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({
|
expect(handleDeleteConversation).toHaveBeenCalledWith('1', expect.objectContaining({
|
||||||
onSuccess: expect.any(Function),
|
onSuccess: expect.any(Function),
|
||||||
@ -502,11 +485,11 @@ describe('Sidebar Index', () => {
|
|||||||
render(<Sidebar />)
|
render(<Sidebar />)
|
||||||
|
|
||||||
await user.click(screen.getByTestId('delete-1'))
|
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(() => {
|
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 />)
|
render(<Sidebar />)
|
||||||
|
|
||||||
await user.click(screen.getByTestId('delete-1'))
|
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))
|
expect(handleDeleteConversation).toHaveBeenCalledWith('1', expect.any(Object))
|
||||||
})
|
})
|
||||||
@ -837,7 +820,7 @@ describe('Sidebar Index', () => {
|
|||||||
|
|
||||||
// Delete it
|
// Delete it
|
||||||
await user.click(screen.getByTestId('delete-1'))
|
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()
|
expect(handleDeleteConversation).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -901,8 +884,8 @@ describe('Sidebar Index', () => {
|
|||||||
try {
|
try {
|
||||||
render(<Sidebar />)
|
render(<Sidebar />)
|
||||||
await user.click(screen.getByTestId('delete-1'))
|
await user.click(screen.getByTestId('delete-1'))
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('share.chat.deleteConversation.title')).toBeInTheDocument()
|
||||||
expect(screen.getByTestId('confirm-content')).toBeEmptyDOMElement()
|
expect(screen.queryByText('share.chat.deleteConversation.content')).not.toBeInTheDocument()
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
useTranslationSpy.mockRestore()
|
useTranslationSpy.mockRestore()
|
||||||
|
|||||||
@ -13,9 +13,17 @@ import ActionButton from '@/app/components/base/action-button'
|
|||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import List from '@/app/components/base/chat/chat-with-history/sidebar/list'
|
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 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 DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||||
import { Button } from '@/app/components/base/ui/button'
|
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 MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
@ -167,15 +175,24 @@ const Sidebar = ({ isPanel, panelVisible }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!!showConfirm && (
|
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && handleCancelConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('chat.deleteConversation.title', { ns: 'share' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={deleteConversationContent}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow
|
{t('chat.deleteConversation.title', { ns: 'share' })}
|
||||||
onCancel={handleCancelConfirm}
|
</AlertDialogTitle>
|
||||||
onConfirm={handleDelete}
|
<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 && (
|
{showRename && (
|
||||||
<RenameModal
|
<RenameModal
|
||||||
isShow
|
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 { useDebounceFn } from 'ahooks'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import { deleteTag, updateTag } from '@/service/tag'
|
import { deleteTag, updateTag } from '@/service/tag'
|
||||||
import { cn } from '@/utils/classnames'
|
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)} />)}
|
{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>
|
</div>
|
||||||
<Confirm
|
<AlertDialog open={showRemoveModal} onOpenChange={open => !open && setShowRemoveModal(false)}>
|
||||||
title={`${t('tag.delete', { ns: 'common' })} "${tag.name}"`}
|
<AlertDialogContent>
|
||||||
isShow={showRemoveModal}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('tag.deleteTip', { ns: 'common' })}
|
<AlertDialogTitle
|
||||||
onConfirm={() => {
|
title={`${t('tag.delete', { ns: 'common' })} "${tag.name}"`}
|
||||||
handleRemove()
|
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
||||||
setShowRemoveModal(false)
|
>
|
||||||
}}
|
{`${t('tag.delete', { ns: 'common' })} "${tag.name}"`}
|
||||||
onCancel={() => setShowRemoveModal(false)}
|
</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(),
|
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
|
// Capture Actions callbacks
|
||||||
let _capturedHandleDelete: (() => void) | undefined
|
let _capturedHandleDelete: (() => void) | undefined
|
||||||
let _capturedHandleExportDSL: (() => void) | undefined
|
let _capturedHandleExportDSL: (() => void) | undefined
|
||||||
@ -182,13 +155,14 @@ describe('TemplateCard', () => {
|
|||||||
type: 'customized' as const,
|
type: 'customized' as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDeleteConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||||
|
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
mockToastSuccess.mockReset()
|
mockToastSuccess.mockReset()
|
||||||
mockToastError.mockReset()
|
mockToastError.mockReset()
|
||||||
mockIsExporting = false
|
mockIsExporting = false
|
||||||
_capturedOnConfirm = undefined
|
|
||||||
_capturedOnCancel = undefined
|
|
||||||
_capturedHandleDelete = undefined
|
_capturedHandleDelete = undefined
|
||||||
_capturedHandleExportDSL = undefined
|
_capturedHandleExportDSL = undefined
|
||||||
_capturedOpenEditModal = undefined
|
_capturedOpenEditModal = undefined
|
||||||
@ -507,7 +481,7 @@ describe('TemplateCard', () => {
|
|||||||
fireEvent.click(deleteButton)
|
fireEvent.click(deleteButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -517,14 +491,13 @@ describe('TemplateCard', () => {
|
|||||||
fireEvent.click(deleteButton)
|
fireEvent.click(deleteButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
const cancelButton = screen.getByTestId('confirm-cancel')
|
fireEvent.click(getDeleteCancelButton())
|
||||||
fireEvent.click(cancelButton)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
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)
|
fireEvent.click(deleteButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
const confirmButton = screen.getByTestId('confirm-submit')
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
fireEvent.click(confirmButton)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeletePipeline).toHaveBeenCalledWith('pipeline-1', expect.any(Object))
|
expect(mockDeletePipeline).toHaveBeenCalledWith('pipeline-1', expect.any(Object))
|
||||||
@ -561,11 +533,10 @@ describe('TemplateCard', () => {
|
|||||||
fireEvent.click(deleteButton)
|
fireEvent.click(deleteButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
const confirmButton = screen.getByTestId('confirm-submit')
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
fireEvent.click(confirmButton)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockInvalidCustomizedTemplateList).toHaveBeenCalled()
|
expect(mockInvalidCustomizedTemplateList).toHaveBeenCalled()
|
||||||
@ -583,14 +554,13 @@ describe('TemplateCard', () => {
|
|||||||
fireEvent.click(deleteButton)
|
fireEvent.click(deleteButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('datasetPipeline.deletePipeline.title')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
const confirmButton = screen.getByTestId('confirm-submit')
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
fireEvent.click(confirmButton)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
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 { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { trackEvent } from '@/app/components/base/amplitude'
|
import { trackEvent } from '@/app/components/base/amplitude'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Modal from '@/app/components/base/modal'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
@ -156,15 +164,24 @@ const TemplateCard = ({
|
|||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
{showDeleteConfirm && (
|
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && onCancelDelete()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('deletePipeline.title', { ns: 'datasetPipeline' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('deletePipeline.content', { ns: 'datasetPipeline' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow={showDeleteConfirm}
|
{t('deletePipeline.title', { ns: 'datasetPipeline' })}
|
||||||
onConfirm={onConfirmDelete}
|
</AlertDialogTitle>
|
||||||
onCancel={onCancelDelete}
|
<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 && (
|
{showDetailModal && (
|
||||||
<Modal
|
<Modal
|
||||||
isShow={showDetailModal}
|
isShow={showDetailModal}
|
||||||
|
|||||||
@ -7,12 +7,20 @@ import { noop } from 'es-toolkit/function'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
import CustomPopover from '@/app/components/base/popover'
|
import CustomPopover from '@/app/components/base/popover'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import { IS_CE_EDITION } from '@/config'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import { DataSourceType, DocumentActionType } from '@/models/datasets'
|
import { DataSourceType, DocumentActionType } from '@/models/datasets'
|
||||||
@ -158,7 +166,7 @@ const Operations = ({ embeddingAvailable, datasetId, detail, selectedIds, onSele
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
: <Switch value={enabled} onChange={v => handleSwitch(v ? 'enable' : 'disable')} size="md" />}
|
: <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 && (
|
{embeddingAvailable && (
|
||||||
@ -280,8 +288,24 @@ const Operations = ({ embeddingAvailable, datasetId, detail, selectedIds, onSele
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{showModal
|
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||||
&& (<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)} />)}
|
<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} />)}
|
{isShowRenameModal && currDocument && (<RenameModal datasetId={datasetId} documentId={currDocument.id} name={currDocument.name} onClose={setShowRenameModalFalse} onSaved={handleRenamed} />)}
|
||||||
</div>
|
</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 { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import BatchAction from '../batch-action'
|
import BatchAction from '../batch-action'
|
||||||
|
|
||||||
@ -105,6 +105,21 @@ describe('BatchAction', () => {
|
|||||||
expect(mockOnBatchDelete).toHaveBeenCalledTimes(1)
|
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
|
// Optional props tests
|
||||||
|
|||||||
@ -3,10 +3,18 @@ import { RiArchive2Line, RiCheckboxCircleLine, RiCloseCircleLine, RiDeleteBinLin
|
|||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
import { Button } from '@/app/components/base/ui/button'
|
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 { IS_CE_EDITION } from '@/config'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
|
|
||||||
@ -147,20 +155,24 @@ const BatchAction: FC<IBatchActionProps> = ({
|
|||||||
<span className="px-0.5">{t(`${i18nPrefix}.cancel`, { ns: 'dataset' })}</span>
|
<span className="px-0.5">{t(`${i18nPrefix}.cancel`, { ns: 'dataset' })}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{
|
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||||
isShowDeleteConfirm && (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||||
content={t('list.delete.content', { ns: 'datasetDocuments' })}
|
</AlertDialogTitle>
|
||||||
confirmText={t('operation.sure', { ns: 'common' })}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
onConfirm={handleBatchDelete}
|
{t('list.delete.content', { ns: 'datasetDocuments' })}
|
||||||
onCancel={hideDeleteConfirm}
|
</AlertDialogDescription>
|
||||||
isLoading={isDeleting}
|
</div>
|
||||||
isDisabled={isDeleting}
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,17 @@ import * as React from 'react'
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Badge from '@/app/components/base/badge'
|
import Badge from '@/app/components/base/badge'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 ImageList from '@/app/components/datasets/common/image-list'
|
||||||
import { ChunkingMode } from '@/models/datasets'
|
import { ChunkingMode } from '@/models/datasets'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
@ -137,7 +144,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||||||
data-testid="segment-card"
|
data-testid="segment-card"
|
||||||
className={cn(
|
className={cn(
|
||||||
'chunk-card group/card w-full rounded-xl px-3',
|
'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' : '',
|
focused.segmentContent ? 'bg-dataset-chunk-detail-card-hover-bg' : '',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@ -170,7 +177,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
|
<StatusItem status={enabled ? 'enabled' : 'disabled'} reverse textCls="text-text-tertiary system-xs-regular" />
|
||||||
{embeddingAvailable && (
|
{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"
|
border-components-actionbar-border bg-components-actionbar-bg p-1 shadow-md backdrop-blur-[5px] group-hover/card:flex"
|
||||||
>
|
>
|
||||||
{!archived && (
|
{!archived && (
|
||||||
@ -254,7 +261,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||||||
? (
|
? (
|
||||||
<button
|
<button
|
||||||
type="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?.()}
|
onClick={() => onClick?.()}
|
||||||
>
|
>
|
||||||
{t('operation.viewMore', { ns: 'common' })}
|
{t('operation.viewMore', { ns: 'common' })}
|
||||||
@ -276,16 +283,21 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{showModal
|
<AlertDialog open={showModal} onOpenChange={open => !open && setShowModal(false)}>
|
||||||
&& (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow={showModal}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('segment.delete', { ns: 'datasetDocuments' })}
|
{t('segment.delete', { ns: 'datasetDocuments' })}
|
||||||
confirmText={t('operation.sure', { ns: 'common' })}
|
</AlertDialogTitle>
|
||||||
onConfirm={async () => { await onDelete?.(id) }}
|
</div>
|
||||||
onCancel={() => setShowModal(false)}
|
<AlertDialogActions>
|
||||||
/>
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
)}
|
<AlertDialogConfirmButton onClick={async () => { await onDelete?.(id) }}>
|
||||||
|
{t('operation.sure', { ns: 'common' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,17 @@ import { RiBook2Line, RiCloseLine, RiInformation2Line, RiLock2Fill } from '@remi
|
|||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
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 { PortalToFollowElem, PortalToFollowElemContent } from '@/app/components/base/portal-to-follow-elem'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { createExternalAPI } from '@/service/datasets'
|
import { createExternalAPI } from '@/service/datasets'
|
||||||
@ -176,7 +184,27 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCan
|
|||||||
{t('externalAPIForm.encrypted.end', { ns: 'dataset' })}
|
{t('externalAPIForm.encrypted.end', { ns: 'dataset' })}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
|
|||||||
@ -8,8 +8,16 @@ import * as React from 'react'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
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 { 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 { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { checkUsageExternalAPI, deleteExternalAPI, fetchExternalAPI, updateExternalAPI } from '@/service/datasets'
|
import { checkUsageExternalAPI, deleteExternalAPI, fetchExternalAPI, updateExternalAPI } from '@/service/datasets'
|
||||||
@ -115,7 +123,7 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({ api
|
|||||||
<ApiConnectionMod className="h-4 w-4" />
|
<ApiConnectionMod className="h-4 w-4" />
|
||||||
<div className="system-sm-medium">{api.name}</div>
|
<div className="system-sm-medium">{api.name}</div>
|
||||||
</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>
|
||||||
<div className="flex items-start gap-1">
|
<div className="flex items-start gap-1">
|
||||||
<ActionButton onClick={handleEditClick}>
|
<ActionButton onClick={handleEditClick}>
|
||||||
@ -131,20 +139,26 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({ api
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showConfirm && (
|
<AlertDialog open={showConfirm} onOpenChange={open => !open && setShowConfirm(false)}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
isShow={showConfirm}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
title={`${t('deleteExternalAPIConfirmWarningContent.title.front', { ns: 'dataset' })} ${api.name}${t('deleteExternalAPIConfirmWarningContent.title.end', { ns: 'dataset' })}`}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
content={
|
{`${t('deleteExternalAPIConfirmWarningContent.title.front', { ns: 'dataset' })} ${api.name}${t('deleteExternalAPIConfirmWarningContent.title.end', { ns: 'dataset' })}`}
|
||||||
usageCount > 0
|
</AlertDialogTitle>
|
||||||
? `${t('deleteExternalAPIConfirmWarningContent.content.front', { ns: 'dataset' })} ${usageCount} ${t('deleteExternalAPIConfirmWarningContent.content.end', { ns: 'dataset' })}`
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
: t('deleteExternalAPIConfirmWarningContent.noConnectionContent', { ns: 'dataset' })
|
{usageCount > 0
|
||||||
}
|
? `${t('deleteExternalAPIConfirmWarningContent.content.front', { ns: 'dataset' })} ${usageCount} ${t('deleteExternalAPIConfirmWarningContent.content.end', { ns: 'dataset' })}`
|
||||||
type="warning"
|
: t('deleteExternalAPIConfirmWarningContent.noConnectionContent', { ns: 'dataset' })}
|
||||||
onConfirm={handleConfirmDelete}
|
</AlertDialogDescription>
|
||||||
onCancel={() => setShowConfirm(false)}
|
</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', () => {
|
describe('DatasetCardModals', () => {
|
||||||
const mockDataset: DataSet = {
|
const mockDataset: DataSet = {
|
||||||
id: 'dataset-1',
|
id: 'dataset-1',
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
import type { DataSet } from '@/models/datasets'
|
import type { DataSet } from '@/models/datasets'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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'
|
import RenameDatasetModal from '../../../rename-modal'
|
||||||
|
|
||||||
type ModalState = {
|
type ModalState = {
|
||||||
@ -39,15 +47,26 @@ const DatasetCardModals = ({
|
|||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{modalState.showConfirmDelete && (
|
<AlertDialog open={modalState.showConfirmDelete} onOpenChange={open => !open && onCloseConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={modalState.confirmMessage}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow={modalState.showConfirmDelete}
|
{t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||||
onConfirm={onConfirmDelete}
|
</AlertDialogTitle>
|
||||||
onCancel={onCloseConfirm}
|
<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 * as React from 'react'
|
||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Drawer from '@/app/components/base/drawer'
|
import Drawer from '@/app/components/base/drawer'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import { Button } from '@/app/components/base/ui/button'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import CreateModal from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
|
import CreateModal from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
@ -95,16 +103,24 @@ const Item: FC<ItemProps> = ({
|
|||||||
<RiDeleteBinLine className="size-4 cursor-pointer" onClick={showDeleteConfirm} />
|
<RiDeleteBinLine className="size-4 cursor-pointer" onClick={showDeleteConfirm} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isShowDeleteConfirm && (
|
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
isShow
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
type="warning"
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('metadata.datasetMetadata.deleteTitle', { ns: 'dataset' })}
|
{t('metadata.datasetMetadata.deleteTitle', { ns: 'dataset' })}
|
||||||
content={t('metadata.datasetMetadata.deleteContent', { ns: 'dataset', name: payload.name })}
|
</AlertDialogTitle>
|
||||||
onConfirm={handleDelete}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
onCancel={hideDeleteConfirm}
|
{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>
|
||||||
</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 userEvent from '@testing-library/user-event'
|
||||||
import { afterEach } from 'vitest'
|
import { afterEach } from 'vitest'
|
||||||
import SecretKeyModal from '../secret-key-modal'
|
import SecretKeyModal from '../secret-key-modal'
|
||||||
@ -477,6 +477,32 @@ describe('SecretKeyModal', () => {
|
|||||||
|
|
||||||
expect(mockDelAppApikey).not.toHaveBeenCalled()
|
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', () => {
|
describe('delete key for dataset', () => {
|
||||||
|
|||||||
@ -7,11 +7,19 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
import { Button } from '@/app/components/base/ui/button'
|
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 { useAppContext } from '@/context/app-context'
|
||||||
import useTimestamp from '@/hooks/use-timestamp'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import {
|
import {
|
||||||
@ -87,6 +95,14 @@ const SecretKeyModal = ({
|
|||||||
return `${token.slice(0, 3)}...${token.slice(-20)}`
|
return `${token.slice(0, 3)}...${token.slice(-20)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteConfirmOpenChange = (open: boolean) => {
|
||||||
|
if (open)
|
||||||
|
return
|
||||||
|
|
||||||
|
setDelKeyId('')
|
||||||
|
setShowConfirmDelete(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={onClose} title={`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`} className={`${s.customModal} flex flex-col px-8`}>
|
<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">
|
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
||||||
@ -135,18 +151,29 @@ const SecretKeyModal = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
||||||
{showConfirmDelete && (
|
<AlertDialog
|
||||||
<Confirm
|
open={showConfirmDelete}
|
||||||
title={`${t('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}`}
|
onOpenChange={handleDeleteConfirmOpenChange}
|
||||||
content={`${t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}`}
|
>
|
||||||
isShow={showConfirmDelete}
|
<AlertDialogContent>
|
||||||
onConfirm={onDel}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onCancel={() => {
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
setDelKeyId('')
|
{t('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}
|
||||||
setShowConfirmDelete(false)
|
</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>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,8 +105,10 @@ describe('Item Component', () => {
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||||
const dialog = screen.getByTestId('confirm-overlay')
|
const dialog = screen.getByRole('alertdialog', {
|
||||||
const confirmButton = within(dialog).getByText('common.operation.delete')
|
name: /common\.operation\.delete.*Test Extension.*\?/i,
|
||||||
|
})
|
||||||
|
const confirmButton = within(dialog).getByRole('button', { name: 'common.operation.delete' })
|
||||||
fireEvent.click(confirmButton)
|
fireEvent.click(confirmButton)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@ -123,8 +125,10 @@ describe('Item Component', () => {
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||||
const dialog = screen.getByTestId('confirm-overlay')
|
const dialog = screen.getByRole('alertdialog', {
|
||||||
const confirmButton = within(dialog).getByText('common.operation.delete')
|
name: /common\.operation\.delete.*Test Extension.*\?/i,
|
||||||
|
})
|
||||||
|
const confirmButton = within(dialog).getByRole('button', { name: 'common.operation.delete' })
|
||||||
fireEvent.click(confirmButton)
|
fireEvent.click(confirmButton)
|
||||||
|
|
||||||
// Assert
|
// 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
|
// Act
|
||||||
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
||||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||||
|
|
||||||
// Assert
|
// 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', () => {
|
it('should not call delete API when canceling deletion', () => {
|
||||||
|
|||||||
@ -6,7 +6,14 @@ import {
|
|||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { deleteApiBasedExtension } from '@/service/common'
|
import { deleteApiBasedExtension } from '@/service/common'
|
||||||
@ -57,18 +64,21 @@ const Item: FC<ItemProps> = ({
|
|||||||
{t('operation.delete', { ns: 'common' })}
|
{t('operation.delete', { ns: 'common' })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{
|
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && setShowDeleteConfirm(false)}>
|
||||||
showDeleteConfirm
|
<AlertDialogContent>
|
||||||
&& (
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
<Confirm
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow={showDeleteConfirm}
|
{`${t('operation.delete', { ns: 'common' })} \u201C${data.name}\u201D?`}
|
||||||
onCancel={() => setShowDeleteConfirm(false)}
|
</AlertDialogTitle>
|
||||||
title={`${t('operation.delete', { ns: 'common' })} “${data.name}”?`}
|
</div>
|
||||||
onConfirm={handleDeleteApiBasedExtension}
|
<AlertDialogActions>
|
||||||
confirmText={t('operation.delete', { ns: 'common' }) || ''}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
/>
|
<AlertDialogConfirmButton onClick={handleDeleteApiBasedExtension}>
|
||||||
)
|
{t('operation.delete', { ns: 'common' }) || ''}
|
||||||
}
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,14 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 {
|
import {
|
||||||
ApiKeyModal,
|
ApiKeyModal,
|
||||||
usePluginAuthAction,
|
usePluginAuthAction,
|
||||||
@ -123,7 +130,7 @@ const Card = ({
|
|||||||
<div className="system-md-semibold text-text-primary">
|
<div className="system-md-semibold text-text-primary">
|
||||||
{renderI18nObject(label)}
|
{renderI18nObject(label)}
|
||||||
</div>
|
</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}
|
{author}
|
||||||
<div className="mx-0.5 text-text-quaternary">/</div>
|
<div className="mx-0.5 text-text-quaternary">/</div>
|
||||||
{name}
|
{name}
|
||||||
@ -135,7 +142,7 @@ const Card = ({
|
|||||||
onUpdate={handleAuthUpdate}
|
onUpdate={handleAuthUpdate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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' })}
|
{t('auth.connectedWorkspace', { ns: 'plugin' })}
|
||||||
<div className="ml-3 h-px grow bg-divider-subtle"></div>
|
<div className="ml-3 h-px grow bg-divider-subtle"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -157,23 +164,27 @@ const Card = ({
|
|||||||
{
|
{
|
||||||
!credentials_list.length && (
|
!credentials_list.length && (
|
||||||
<div className="p-3 pt-1">
|
<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' })}
|
{t('auth.emptyAuth', { ns: 'plugin' })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
<AlertDialog open={!!deleteCredentialId} onOpenChange={open => !open && closeConfirm()}>
|
||||||
deleteCredentialId && (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||||
isDisabled={doingAction}
|
</AlertDialogTitle>
|
||||||
onCancel={closeConfirm}
|
</div>
|
||||||
onConfirm={handleConfirm}
|
<AlertDialogActions>
|
||||||
/>
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
)
|
<AlertDialogConfirmButton disabled={doingAction} onClick={handleConfirm}>
|
||||||
}
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
{
|
{
|
||||||
!!editValues && (
|
!!editValues && (
|
||||||
<ApiKeyModal
|
<ApiKeyModal
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { Credential, CustomModel, ModelProvider } from '../../../declarations'
|
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 { ConfigurationMethodEnum, ModelTypeEnum } from '../../../declarations'
|
||||||
import Authorized from '../index'
|
import Authorized from '../index'
|
||||||
|
|
||||||
@ -198,4 +198,39 @@ describe('Authorized', () => {
|
|||||||
fireEvent.click(screen.getByRole('button', { name: /common.operation.confirm/i }))
|
fireEvent.click(screen.getByRole('button', { name: /common.operation.confirm/i }))
|
||||||
expect(mockHandleConfirmDelete).toHaveBeenCalled()
|
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,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} 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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
import { useAuth } from '../hooks'
|
import { useAuth } from '../hooks'
|
||||||
@ -240,17 +247,21 @@ const Authorized = ({
|
|||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
{
|
<AlertDialog open={!!deleteCredentialId} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||||
deleteCredentialId && (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('modelProvider.confirmDelete', { ns: 'common' })}
|
{t('modelProvider.confirmDelete', { ns: 'common' })}
|
||||||
isDisabled={doingAction}
|
</AlertDialogTitle>
|
||||||
onCancel={closeConfirmDelete}
|
</div>
|
||||||
onConfirm={handleConfirmDelete}
|
<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 type { Credential, CustomConfigurationModelFixedFields, ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations'
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||||
@ -266,7 +273,21 @@ const ModelLoadBalancingModal = ({ provider, configurateMethod, currentCustomCon
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</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,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} 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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
@ -318,17 +325,21 @@ const Authorized = ({
|
|||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
{
|
<AlertDialog open={!!deleteCredentialId} onOpenChange={open => !open && closeConfirm()}>
|
||||||
deleteCredentialId && (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||||
isDisabled={doingAction}
|
</AlertDialogTitle>
|
||||||
onCancel={closeConfirm}
|
</div>
|
||||||
onConfirm={handleConfirm}
|
<AlertDialogActions>
|
||||||
/>
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
)
|
<AlertDialogConfirmButton disabled={doingAction} onClick={handleConfirm}>
|
||||||
}
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
|
</AlertDialogConfirmButton>
|
||||||
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
{
|
{
|
||||||
!!editValues && (
|
!!editValues && (
|
||||||
<ApiKeyModal
|
<ApiKeyModal
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { EndpointListItem, PluginDetail } from '../../types'
|
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 { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import EndpointCard from '../endpoint-card'
|
import EndpointCard from '../endpoint-card'
|
||||||
|
|
||||||
@ -136,7 +136,6 @@ const mockPluginDetail: PluginDetail = {
|
|||||||
describe('EndpointCard', () => {
|
describe('EndpointCard', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
vi.useFakeTimers()
|
|
||||||
// Reset failure flags
|
// Reset failure flags
|
||||||
failureFlags.enable = false
|
failureFlags.enable = false
|
||||||
failureFlags.disable = false
|
failureFlags.disable = false
|
||||||
@ -152,6 +151,12 @@ describe('EndpointCard', () => {
|
|||||||
vi.useRealTimers()
|
vi.useRealTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const waitForAlertDialogToClose = async () => {
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
it('should render endpoint name', () => {
|
it('should render endpoint name', () => {
|
||||||
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||||
@ -243,6 +248,7 @@ describe('EndpointCard', () => {
|
|||||||
|
|
||||||
describe('Copy Functionality', () => {
|
describe('Copy Functionality', () => {
|
||||||
it('should reset copy state after timeout', async () => {
|
it('should reset copy state after timeout', async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||||
|
|
||||||
const allButtons = screen.getAllByRole('button')
|
const allButtons = screen.getAllByRole('button')
|
||||||
@ -276,19 +282,19 @@ describe('EndpointCard', () => {
|
|||||||
expect(mockHandleChange).toHaveBeenCalled()
|
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} />)
|
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('switch'))
|
fireEvent.click(screen.getByRole('switch'))
|
||||||
expect(screen.getByText('plugin.detailPanel.endpointDisableTip')).toBeInTheDocument()
|
expect(screen.getByText('plugin.detailPanel.endpointDisableTip')).toBeInTheDocument()
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||||
|
await waitForAlertDialogToClose()
|
||||||
|
|
||||||
// Confirm should be hidden
|
expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true')
|
||||||
expect(screen.queryByText('plugin.detailPanel.endpointDisableTip')).not.toBeInTheDocument()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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} />)
|
render(<EndpointCard pluginDetail={mockPluginDetail} data={mockEndpointData} handleChange={mockHandleChange} />)
|
||||||
|
|
||||||
const allButtons = screen.getAllByRole('button')
|
const allButtons = screen.getAllByRole('button')
|
||||||
@ -296,8 +302,7 @@ describe('EndpointCard', () => {
|
|||||||
expect(screen.getByText('plugin.detailPanel.endpointDeleteTip')).toBeInTheDocument()
|
expect(screen.getByText('plugin.detailPanel.endpointDeleteTip')).toBeInTheDocument()
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||||
|
await waitForAlertDialogToClose()
|
||||||
expect(screen.queryByText('plugin.detailPanel.endpointDeleteTip')).not.toBeInTheDocument()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should hide edit modal when cancel clicked', () => {
|
it('should hide edit modal when cancel clicked', () => {
|
||||||
|
|||||||
@ -6,10 +6,17 @@ import * as React from 'react'
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
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 { CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||||
@ -122,6 +129,14 @@ const EndpointCard = ({
|
|||||||
setIsCopied(true)
|
setIsCopied(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDisableConfirmOpenChange = (open: boolean) => {
|
||||||
|
if (open)
|
||||||
|
return
|
||||||
|
|
||||||
|
hideDisableConfirm()
|
||||||
|
setActive(true)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCopied) {
|
if (isCopied) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@ -139,7 +154,7 @@ const EndpointCard = ({
|
|||||||
<div className="rounded-xl bg-background-section-burn p-0.5">
|
<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="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="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" />
|
<RiLoginCircleLine className="h-4 w-4" />
|
||||||
<div>{data.name}</div>
|
<div>{data.name}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -154,8 +169,8 @@ const EndpointCard = ({
|
|||||||
</div>
|
</div>
|
||||||
{data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
|
{data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
|
||||||
<div key={index} className="flex h-6 items-center">
|
<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="w-12 shrink-0 system-xs-regular text-text-tertiary">{endpoint.method}</div>
|
||||||
<div className="group/item flex grow items-center truncate text-text-secondary system-xs-regular">
|
<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>
|
<div title={`${data.url}${endpoint.path}`} className="truncate">{`${data.url}${endpoint.path}`}</div>
|
||||||
<Tooltip popupContent={t(`operation.${isCopied ? 'copied' : 'copy'}`, { ns: 'common' })} position="top">
|
<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}`)}>
|
<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>
|
||||||
<div className="flex items-center justify-between p-2 pl-3">
|
<div className="flex items-center justify-between p-2 pl-3">
|
||||||
{active && (
|
{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" />
|
<Indicator color="green" />
|
||||||
{t('detailPanel.serviceOk', { ns: 'plugin' })}
|
{t('detailPanel.serviceOk', { ns: 'plugin' })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!active && (
|
{!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" />
|
<Indicator color="gray" />
|
||||||
{t('detailPanel.disabled', { ns: 'plugin' })}
|
{t('detailPanel.disabled', { ns: 'plugin' })}
|
||||||
</div>
|
</div>
|
||||||
@ -186,27 +201,47 @@ const EndpointCard = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isShowDisableConfirm && (
|
<AlertDialog
|
||||||
<Confirm
|
open={isShowDisableConfirm}
|
||||||
isShow
|
onOpenChange={handleDisableConfirmOpenChange}
|
||||||
title={t('detailPanel.endpointDisableTip', { ns: 'plugin' })}
|
>
|
||||||
content={<div>{t('detailPanel.endpointDisableContent', { ns: 'plugin', name: data.name })}</div>}
|
<AlertDialogContent>
|
||||||
onCancel={() => {
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
hideDisableConfirm()
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
setActive(true)
|
{t('detailPanel.endpointDisableTip', { ns: 'plugin' })}
|
||||||
}}
|
</AlertDialogTitle>
|
||||||
onConfirm={() => disableEndpoint(endpointID)}
|
<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>
|
||||||
{isShowDeleteConfirm && (
|
</div>
|
||||||
<Confirm
|
<AlertDialogActions>
|
||||||
isShow
|
<AlertDialogCancelButton>
|
||||||
title={t('detailPanel.endpointDeleteTip', { ns: 'plugin' })}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
content={<div>{t('detailPanel.endpointDeleteContent', { ns: 'plugin', name: data.name })}</div>}
|
</AlertDialogCancelButton>
|
||||||
onCancel={hideDeleteConfirm}
|
<AlertDialogConfirmButton onClick={() => disableEndpoint(endpointID)}>
|
||||||
onConfirm={() => deleteEndpoint(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 && (
|
{isShowEndpointModal && (
|
||||||
<EndpointModal
|
<EndpointModal
|
||||||
formSchemas={formSchemas as any}
|
formSchemas={formSchemas as any}
|
||||||
|
|||||||
@ -59,18 +59,18 @@ export const DeleteConfirm = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<AlertDialog open={isShow} onOpenChange={handleOpenChange}>
|
<AlertDialog open={isShow} onOpenChange={handleOpenChange}>
|
||||||
<AlertDialogContent backdropProps={{ forceRender: true }}>
|
<AlertDialogContent backdropProps={{ forceRender: true }}>
|
||||||
<div className="flex flex-col gap-2 px-6 pb-4 pt-6">
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
<AlertDialogTitle title={t(`${tPrefix}.title`, { ns: 'pluginTrigger', name: currentName })} className="w-full truncate text-text-primary title-2xl-semi-bold">
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
{t(`${tPrefix}.title`, { ns: 'pluginTrigger', name: currentName })}
|
{t(`${tPrefix}.title`, { ns: 'pluginTrigger', name: currentName })}
|
||||||
</AlertDialogTitle>
|
</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
|
{workflowsInUse > 0
|
||||||
? t(`${tPrefix}.contentWithApps`, { ns: 'pluginTrigger', count: workflowsInUse })
|
? t(`${tPrefix}.contentWithApps`, { ns: 'pluginTrigger', count: workflowsInUse })
|
||||||
: t(`${tPrefix}.content`, { ns: 'pluginTrigger' })}
|
: t(`${tPrefix}.content`, { ns: 'pluginTrigger' })}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
{workflowsInUse > 0 && (
|
{workflowsInUse > 0 && (
|
||||||
<div className="mt-6">
|
<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 })}
|
{t(`${tPrefix}.confirmInputTip`, { ns: 'pluginTrigger', name: currentName })}
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<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 ====================
|
// ==================== Test Utilities ====================
|
||||||
|
|
||||||
type ActionProps = {
|
type ActionProps = {
|
||||||
@ -151,6 +127,9 @@ const createActionProps = (overrides: Partial<ActionProps> = {}): ActionProps =>
|
|||||||
...overrides,
|
...overrides,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getDeleteConfirmButton = () => screen.getByRole('button', { name: /common\.operation\.confirm/ })
|
||||||
|
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||||
|
|
||||||
// ==================== Tests ====================
|
// ==================== Tests ====================
|
||||||
|
|
||||||
// Helper to find action buttons (real ActionButton component uses type="button")
|
// Helper to find action buttons (real ActionButton component uses type="button")
|
||||||
@ -277,8 +256,7 @@ describe('Action Component', () => {
|
|||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(screen.getByTestId('confirm-modal')).toBeInTheDocument()
|
expect(screen.getByText('plugin.action.delete')).toBeInTheDocument()
|
||||||
expect(screen.getByTestId('confirm-title')).toHaveTextContent('plugin.action.delete')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should display plugin name in delete confirm content', () => {
|
it('should display plugin name in delete confirm content', () => {
|
||||||
@ -309,12 +287,14 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
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
|
// 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 () => {
|
it('should call uninstallPlugin when confirm is clicked', async () => {
|
||||||
@ -329,7 +309,7 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -351,7 +331,7 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -373,7 +353,7 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -395,7 +375,7 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -422,17 +402,17 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
// Assert - Loading state
|
// Assert - Loading state
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-modal')).toHaveAttribute('data-loading', 'true')
|
expect(getDeleteConfirmButton()).toBeDisabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Resolve and check modal closes
|
// Resolve and check modal closes
|
||||||
resolveUninstall!({ success: true })
|
resolveUninstall!({ success: true })
|
||||||
await waitFor(() => {
|
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
|
// Act - First render and delete
|
||||||
const { rerender } = render(<Action {...props} />)
|
const { rerender } = render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('stable-install-id')
|
expect(mockUninstallPlugin).toHaveBeenCalledWith('stable-install-id')
|
||||||
@ -709,7 +689,7 @@ describe('Action Component', () => {
|
|||||||
mockUninstallPlugin.mockClear()
|
mockUninstallPlugin.mockClear()
|
||||||
rerender(<Action {...props} />)
|
rerender(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('stable-install-id')
|
expect(mockUninstallPlugin).toHaveBeenCalledWith('stable-install-id')
|
||||||
@ -735,7 +715,7 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
const { rerender } = render(<Action {...props1} />)
|
const { rerender } = render(<Action {...props1} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('install-1')
|
expect(mockUninstallPlugin).toHaveBeenCalledWith('install-1')
|
||||||
@ -744,7 +724,7 @@ describe('Action Component', () => {
|
|||||||
mockUninstallPlugin.mockClear()
|
mockUninstallPlugin.mockClear()
|
||||||
rerender(<Action {...props2} />)
|
rerender(<Action {...props2} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('install-2')
|
expect(mockUninstallPlugin).toHaveBeenCalledWith('install-2')
|
||||||
@ -772,7 +752,7 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
const { rerender } = render(<Action {...props1} />)
|
const { rerender } = render(<Action {...props1} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(onDelete1).toHaveBeenCalled()
|
expect(onDelete1).toHaveBeenCalled()
|
||||||
@ -781,7 +761,7 @@ describe('Action Component', () => {
|
|||||||
|
|
||||||
rerender(<Action {...props2} />)
|
rerender(<Action {...props2} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(onDelete2).toHaveBeenCalled()
|
expect(onDelete2).toHaveBeenCalled()
|
||||||
@ -847,17 +827,16 @@ describe('Action Component', () => {
|
|||||||
// Act
|
// Act
|
||||||
render(<Action {...props} />)
|
render(<Action {...props} />)
|
||||||
fireEvent.click(getActionButtons()[0])
|
fireEvent.click(getActionButtons()[0])
|
||||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
|
|
||||||
// The confirm button should be disabled during deletion
|
// The confirm button should be disabled during deletion
|
||||||
expect(screen.getByTestId('confirm-modal')).toHaveAttribute('data-loading', 'true')
|
expect(getDeleteConfirmButton()).toBeDisabled()
|
||||||
expect(screen.getByTestId('confirm-modal')).toHaveAttribute('data-disabled', 'true')
|
|
||||||
|
|
||||||
// Resolve the deletion
|
// Resolve the deletion
|
||||||
resolveFirst!({ success: true })
|
resolveFirst!({ success: true })
|
||||||
|
|
||||||
await waitFor(() => {
|
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 * as React from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { toast } from '@/app/components/base/ui/toast'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { uninstallPlugin } from '@/service/plugins'
|
import { uninstallPlugin } from '@/service/plugins'
|
||||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||||
import ActionButton from '../../base/action-button'
|
import ActionButton from '../../base/action-button'
|
||||||
import Confirm from '../../base/confirm'
|
|
||||||
import Tooltip from '../../base/tooltip'
|
import Tooltip from '../../base/tooltip'
|
||||||
import { checkForUpdates, fetchReleases } from '../install-plugin/hooks'
|
import { checkForUpdates, fetchReleases } from '../install-plugin/hooks'
|
||||||
import PluginInfo from '../plugin-page/plugin-info'
|
import PluginInfo from '../plugin-page/plugin-info'
|
||||||
@ -151,24 +158,27 @@ const Action: FC<Props> = ({
|
|||||||
onHide={hidePluginInfo}
|
onHide={hidePluginInfo}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Confirm
|
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||||
isShow={isShowDeleteConfirm}
|
<AlertDialogContent>
|
||||||
title={t(`${i18nPrefix}.delete`, { ns: 'plugin' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={(
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
<div>
|
{t(`${i18nPrefix}.delete`, { ns: 'plugin' })}
|
||||||
{t(`${i18nPrefix}.deleteContentLeft`, { ns: 'plugin' })}
|
</AlertDialogTitle>
|
||||||
<span className="system-md-semibold">{pluginName}</span>
|
<div className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
{t(`${i18nPrefix}.deleteContentRight`, { ns: 'plugin' })}
|
{t(`${i18nPrefix}.deleteContentLeft`, { ns: 'plugin' })}
|
||||||
<br />
|
<span className="system-md-semibold">{pluginName}</span>
|
||||||
{/* // todo: add usedInApps */}
|
{t(`${i18nPrefix}.deleteContentRight`, { ns: 'plugin' })}
|
||||||
{/* {usedInApps > 0 && t(`${i18nPrefix}.usedInApps`, { num: usedInApps })} */}
|
<br />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<AlertDialogActions>
|
||||||
onCancel={hideDeleteConfirm}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
onConfirm={handleDelete}
|
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={handleDelete}>
|
||||||
isLoading={deleting}
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
isDisabled={deleting}
|
</AlertDialogConfirmButton>
|
||||||
/>
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import Conversion from '../conversion'
|
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', () => ({
|
vi.mock('../screenshot', () => ({
|
||||||
default: () => <div data-testid="screenshot" />,
|
default: () => <div data-testid="screenshot" />,
|
||||||
}))
|
}))
|
||||||
@ -112,11 +89,10 @@ describe('Conversion', () => {
|
|||||||
it('should show confirm modal when convert button clicked', () => {
|
it('should show confirm modal when convert button clicked', () => {
|
||||||
render(<Conversion />)
|
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'))
|
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
||||||
|
|
||||||
expect(screen.getByTestId('confirm-modal')).toBeInTheDocument()
|
|
||||||
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -124,17 +100,19 @@ describe('Conversion', () => {
|
|||||||
render(<Conversion />)
|
render(<Conversion />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
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'))
|
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||||
expect(screen.queryByTestId('confirm-modal')).not.toBeInTheDocument()
|
return waitFor(() => {
|
||||||
|
expect(screen.queryByText('datasetPipeline.conversion.confirm.title')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call convert when confirm is clicked', () => {
|
it('should call convert when confirm is clicked', () => {
|
||||||
render(<Conversion />)
|
render(<Conversion />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
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({
|
expect(mockConvert).toHaveBeenCalledWith('ds-123', expect.objectContaining({
|
||||||
onSuccess: expect.any(Function),
|
onSuccess: expect.any(Function),
|
||||||
@ -150,7 +128,7 @@ describe('Conversion', () => {
|
|||||||
render(<Conversion />)
|
render(<Conversion />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
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(mockToast.success).toHaveBeenCalledWith('datasetPipeline.conversion.successMessage')
|
||||||
expect(mockInvalidDatasetDetail).toHaveBeenCalled()
|
expect(mockInvalidDatasetDetail).toHaveBeenCalled()
|
||||||
@ -164,7 +142,7 @@ describe('Conversion', () => {
|
|||||||
render(<Conversion />)
|
render(<Conversion />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
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')
|
expect(mockToast.error).toHaveBeenCalledWith('datasetPipeline.conversion.errorMessage')
|
||||||
})
|
})
|
||||||
@ -177,7 +155,7 @@ describe('Conversion', () => {
|
|||||||
render(<Conversion />)
|
render(<Conversion />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('datasetPipeline.operations.convert'))
|
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')
|
expect(mockToast.error).toHaveBeenCalledWith('datasetPipeline.conversion.errorMessage')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -472,21 +472,23 @@ describe('Conversion', () => {
|
|||||||
const convertButton = screen.getByRole('button', { name: /datasetPipeline\.operations\.convert/i })
|
const convertButton = screen.getByRole('button', { name: /datasetPipeline\.operations\.convert/i })
|
||||||
fireEvent.click(convertButton)
|
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.title')).toBeInTheDocument()
|
||||||
expect(screen.getByText('datasetPipeline.conversion.confirm.content')).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 />)
|
render(<Conversion />)
|
||||||
|
|
||||||
const convertButton = screen.getByRole('button', { name: /datasetPipeline\.operations\.convert/i })
|
const convertButton = screen.getByRole('button', { name: /datasetPipeline\.operations\.convert/i })
|
||||||
fireEvent.click(convertButton)
|
fireEvent.click(convertButton)
|
||||||
expect(screen.getByText('datasetPipeline.conversion.confirm.title')).toBeInTheDocument()
|
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' }))
|
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 * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { useParams } from '@/next/navigation'
|
import { useParams } from '@/next/navigation'
|
||||||
@ -39,6 +47,8 @@ const Conversion = () => {
|
|||||||
const handleCancelConversion = useCallback(() => {
|
const handleCancelConversion = useCallback(() => {
|
||||||
setShowConfirmModal(false)
|
setShowConfirmModal(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
const confirmTitle = t('conversion.confirm.title', { ns: 'datasetPipeline' })
|
||||||
|
const confirmContent = t('conversion.confirm.content', { ns: 'datasetPipeline' })
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center bg-background-body p-6 pb-16">
|
<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">
|
<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>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -688,15 +688,11 @@ describe('publisher', () => {
|
|||||||
expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument()
|
expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
const cancelButtons = screen.getAllByRole('button')
|
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||||
const cancelButton = cancelButtons.find(btn =>
|
|
||||||
btn.className.includes('cancel') || btn.textContent?.includes('Cancel'),
|
|
||||||
)
|
|
||||||
if (cancelButton)
|
|
||||||
fireEvent.click(cancelButton)
|
|
||||||
|
|
||||||
// Note: This test verifies the confirm modal can be displayed
|
await waitFor(() => {
|
||||||
expect(screen.getByText('pipeline.common.confirmPublishContent')).toBeInTheDocument()
|
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should publish when confirm is clicked in confirm modal', async () => {
|
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.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', () => ({
|
vi.mock('@/app/components/base/divider', () => ({
|
||||||
default: () => <hr />,
|
default: () => <hr />,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -5,10 +5,18 @@ import { useBoolean, useKeyPress } from 'ahooks'
|
|||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { trackEvent } from '@/app/components/base/amplitude'
|
import { trackEvent } from '@/app/components/base/amplitude'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
||||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { useChecklistBeforePublish } from '@/app/components/workflow/hooks'
|
import { useChecklistBeforePublish } from '@/app/components/workflow/hooks'
|
||||||
@ -224,7 +232,29 @@ const Popup = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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} />)}
|
{showPublishAsKnowledgePipelineModal && (<PublishAsKnowledgePipelineModal confirmDisabled={isPublishingAsCustomizedPipeline} onConfirm={handlePublishAsKnowledgePipeline} onCancel={hidePublishAsKnowledgePipelineModal} />)}
|
||||||
</div>
|
</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
|
// Mock the OperationDropdown
|
||||||
type OperationDropdownProps = {
|
type OperationDropdownProps = {
|
||||||
onEdit: () => void
|
onEdit: () => void
|
||||||
@ -172,6 +147,9 @@ describe('MCPCard', () => {
|
|||||||
onDeleted: vi.fn(),
|
onDeleted: vi.fn(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDeleteConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||||
|
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockUpdateMCP.mockClear()
|
mockUpdateMCP.mockClear()
|
||||||
mockDeleteMCP.mockClear()
|
mockDeleteMCP.mockClear()
|
||||||
@ -450,7 +428,7 @@ describe('MCPCard', () => {
|
|||||||
|
|
||||||
// Confirm dialog should be shown
|
// Confirm dialog should be shown
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -462,15 +440,14 @@ describe('MCPCard', () => {
|
|||||||
fireEvent.click(removeBtn)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
const cancelBtn = screen.getByTestId('cancel-delete-btn')
|
fireEvent.click(getDeleteCancelButton())
|
||||||
fireEvent.click(cancelBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
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)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Confirm delete
|
// Confirm delete
|
||||||
const confirmBtn = screen.getByTestId('confirm-delete-btn')
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
fireEvent.click(confirmBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeleteMCP).toHaveBeenCalledWith('mcp-1')
|
expect(mockDeleteMCP).toHaveBeenCalledWith('mcp-1')
|
||||||
@ -506,12 +482,11 @@ describe('MCPCard', () => {
|
|||||||
fireEvent.click(removeBtn)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Confirm delete
|
// Confirm delete
|
||||||
const confirmBtn = screen.getByTestId('confirm-delete-btn')
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
fireEvent.click(confirmBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeleteMCP).toHaveBeenCalled()
|
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
|
// Mock OperationDropdown
|
||||||
vi.mock('../operation-dropdown', () => ({
|
vi.mock('../operation-dropdown', () => ({
|
||||||
default: ({ onEdit, onRemove }: { onEdit: () => void, onRemove: () => void }) => (
|
default: ({ onEdit, onRemove }: { onEdit: () => void, onRemove: () => void }) => (
|
||||||
@ -165,6 +151,9 @@ describe('MCPDetailContent', () => {
|
|||||||
React.createElement(QueryClientProvider, { client: queryClient }, children)
|
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 => ({
|
const createMockDetail = (overrides = {}): ToolWithProvider => ({
|
||||||
id: 'mcp-1',
|
id: 'mcp-1',
|
||||||
name: 'Test MCP Server',
|
name: 'Test MCP Server',
|
||||||
@ -494,7 +483,7 @@ describe('MCPDetailContent', () => {
|
|||||||
fireEvent.click(updateBtn)
|
fireEvent.click(updateBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.toolUpdateConfirmTitle')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -514,12 +503,11 @@ describe('MCPDetailContent', () => {
|
|||||||
fireEvent.click(updateBtn)
|
fireEvent.click(updateBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.toolUpdateConfirmTitle')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Confirm the update
|
// Confirm the update
|
||||||
const confirmBtn = screen.getByTestId('confirm-btn')
|
fireEvent.click(getConfirmButton())
|
||||||
fireEvent.click(confirmBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockUpdateTools).toHaveBeenCalledWith('mcp-1')
|
expect(mockUpdateTools).toHaveBeenCalledWith('mcp-1')
|
||||||
@ -636,7 +624,7 @@ describe('MCPDetailContent', () => {
|
|||||||
fireEvent.click(removeBtn)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -648,15 +636,14 @@ describe('MCPDetailContent', () => {
|
|||||||
fireEvent.click(removeBtn)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
const cancelBtn = screen.getByTestId('cancel-btn')
|
fireEvent.click(getCancelButton())
|
||||||
fireEvent.click(cancelBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
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)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Confirm delete
|
// Confirm delete
|
||||||
const confirmBtn = screen.getByTestId('confirm-btn')
|
fireEvent.click(getConfirmButton())
|
||||||
fireEvent.click(confirmBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeleteMCP).toHaveBeenCalledWith('mcp-1')
|
expect(mockDeleteMCP).toHaveBeenCalledWith('mcp-1')
|
||||||
@ -692,12 +678,11 @@ describe('MCPDetailContent', () => {
|
|||||||
fireEvent.click(removeBtn)
|
fireEvent.click(removeBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.delete')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Confirm delete
|
// Confirm delete
|
||||||
const confirmBtn = screen.getByTestId('confirm-btn')
|
fireEvent.click(getConfirmButton())
|
||||||
fireEvent.click(confirmBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeleteMCP).toHaveBeenCalled()
|
expect(mockDeleteMCP).toHaveBeenCalled()
|
||||||
@ -840,15 +825,14 @@ describe('MCPDetailContent', () => {
|
|||||||
fireEvent.click(updateBtn)
|
fireEvent.click(updateBtn)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.mcp.toolUpdateConfirmTitle')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Cancel the update
|
// Cancel the update
|
||||||
const cancelBtn = screen.getByTestId('cancel-btn')
|
fireEvent.click(getCancelButton())
|
||||||
fireEvent.click(cancelBtn)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
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 { useCallback, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||||
@ -286,30 +294,42 @@ const MCPDetailContent: FC<Props> = ({
|
|||||||
onHide={hideUpdateModal}
|
onHide={hideUpdateModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isShowDeleteConfirm && (
|
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
isShow
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
title={t('mcp.delete', { ns: 'tools' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
content={(
|
{t('mcp.delete', { ns: 'tools' })}
|
||||||
<div>
|
</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 })}
|
{t('mcp.deleteConfirmTitle', { ns: 'tools', mcp: detail.name })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
onCancel={hideDeleteConfirm}
|
<AlertDialogActions>
|
||||||
onConfirm={handleDelete}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
isLoading={deleting}
|
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={handleDelete}>
|
||||||
isDisabled={deleting}
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
/>
|
</AlertDialogConfirmButton>
|
||||||
)}
|
</AlertDialogActions>
|
||||||
{isShowUpdateConfirm && (
|
</AlertDialogContent>
|
||||||
<Confirm
|
</AlertDialog>
|
||||||
isShow
|
<AlertDialog open={isShowUpdateConfirm} onOpenChange={open => !open && hideUpdateConfirm()}>
|
||||||
title={t('mcp.toolUpdateConfirmTitle', { ns: 'tools' })}
|
<AlertDialogContent>
|
||||||
content={t('mcp.toolUpdateConfirmContent', { ns: 'tools' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onCancel={hideUpdateConfirm}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
onConfirm={handleUpdateTools}
|
{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 { RiEditLine, RiLoopLeftLine } from '@remixicon/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal'
|
import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal'
|
||||||
@ -295,16 +303,24 @@ const MCPServiceCard: FC<IAppCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showConfirmDelete && (
|
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
type="warning"
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
title={t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
content={t('mcp.server.reGen', { ns: 'tools' })}
|
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||||
isShow={showConfirmDelete}
|
</AlertDialogTitle>
|
||||||
onConfirm={onConfirmRegenerate}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
onCancel={closeConfirmDelete}
|
{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 { useBoolean } from 'ahooks'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 Indicator from '@/app/components/header/indicator'
|
||||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
@ -87,34 +94,34 @@ const MCPCard = ({
|
|||||||
<Icon src={data.icon} />
|
<Icon src={data.icon} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grow">
|
<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 className="system-xs-regular text-text-tertiary">{data.server_identifier}</div>
|
||||||
</div>
|
</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 w-0 grow items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<RiHammerFill className="h-3 w-3 shrink-0 text-text-quaternary" />
|
<RiHammerFill className="h-3 w-3 shrink-0 text-text-quaternary" />
|
||||||
{data.tools.length > 0 && (
|
{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 && (
|
{!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>
|
||||||
<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 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>
|
</div>
|
||||||
{data.is_team_authorization && data.tools.length > 0 && <Indicator color="green" className="shrink-0" />}
|
{data.is_team_authorization && data.tools.length > 0 && <Indicator color="green" className="shrink-0" />}
|
||||||
{(!data.is_team_authorization || !data.tools.length) && (
|
{(!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' })}
|
{t('mcp.noConfigured', { ns: 'tools' })}
|
||||||
<Indicator color="red" />
|
<Indicator color="red" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isCurrentWorkspaceManager && (
|
{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
|
<OperationDropdown
|
||||||
inCard
|
inCard
|
||||||
onOpenChange={setIsOperationShow}
|
onOpenChange={setIsOperationShow}
|
||||||
@ -131,21 +138,24 @@ const MCPCard = ({
|
|||||||
onHide={hideUpdateModal}
|
onHide={hideUpdateModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isShowDeleteConfirm && (
|
<AlertDialog open={isShowDeleteConfirm} onOpenChange={open => !open && hideDeleteConfirm()}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
isShow
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
title={t('mcp.delete', { ns: 'tools' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
content={(
|
{t('mcp.delete', { ns: 'tools' })}
|
||||||
<div>
|
</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 })}
|
{t('mcp.deleteConfirmTitle', { ns: 'tools', mcp: data.name })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
onCancel={hideDeleteConfirm}
|
<AlertDialogActions>
|
||||||
onConfirm={handleDelete}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
isLoading={deleting}
|
<AlertDialogConfirmButton loading={deleting} disabled={deleting} onClick={handleDelete}>
|
||||||
isDisabled={deleting}
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
/>
|
</AlertDialogConfirmButton>
|
||||||
)}
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,19 +79,6 @@ vi.mock('@/app/components/base/drawer', () => ({
|
|||||||
isOpen ? <div data-testid="drawer">{children}</div> : null,
|
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 mockToastSuccess = vi.hoisted(() => vi.fn())
|
||||||
const mockToastError = vi.hoisted(() => vi.fn())
|
const mockToastError = vi.hoisted(() => vi.fn())
|
||||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||||
@ -170,6 +157,9 @@ const createMockCollection = (overrides?: Partial<Collection>): Collection => ({
|
|||||||
...overrides,
|
...overrides,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getDeleteConfirmButton = () => screen.getByRole('button', { name: 'common.operation.confirm' })
|
||||||
|
const getDeleteCancelButton = () => screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||||
|
|
||||||
describe('ProviderDetail', () => {
|
describe('ProviderDetail', () => {
|
||||||
const mockOnHide = vi.fn()
|
const mockOnHide = vi.fn()
|
||||||
const mockOnRefreshData = vi.fn()
|
const mockOnRefreshData = vi.fn()
|
||||||
@ -552,9 +542,9 @@ describe('ProviderDetail', () => {
|
|||||||
})
|
})
|
||||||
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
||||||
fireEvent.click(screen.getByTestId('edit-remove'))
|
fireEvent.click(screen.getByTestId('edit-remove'))
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.createTool.deleteToolConfirmTitle')).toBeInTheDocument()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
})
|
})
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockRemoveCustomCollection).toHaveBeenCalledWith('test-collection')
|
expect(mockRemoveCustomCollection).toHaveBeenCalledWith('test-collection')
|
||||||
@ -626,9 +616,9 @@ describe('ProviderDetail', () => {
|
|||||||
})
|
})
|
||||||
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
||||||
fireEvent.click(screen.getByTestId('wf-remove'))
|
fireEvent.click(screen.getByTestId('wf-remove'))
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.createTool.deleteToolConfirmTitle')).toBeInTheDocument()
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
fireEvent.click(screen.getByTestId('confirm-btn'))
|
fireEvent.click(getDeleteConfirmButton())
|
||||||
})
|
})
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockDeleteWorkflowTool).toHaveBeenCalledWith('test-id')
|
expect(mockDeleteWorkflowTool).toHaveBeenCalledWith('test-id')
|
||||||
@ -710,9 +700,11 @@ describe('ProviderDetail', () => {
|
|||||||
})
|
})
|
||||||
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
fireEvent.click(screen.getByText('tools.createTool.editAction'))
|
||||||
fireEvent.click(screen.getByTestId('edit-remove'))
|
fireEvent.click(screen.getByTestId('edit-remove'))
|
||||||
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument()
|
expect(screen.getByText('tools.createTool.deleteToolConfirmTitle')).toBeInTheDocument()
|
||||||
fireEvent.click(screen.getByTestId('cancel-btn'))
|
fireEvent.click(getDeleteCancelButton())
|
||||||
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument()
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -8,10 +8,18 @@ import * as React from 'react'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
|
||||||
import Drawer from '@/app/components/base/drawer'
|
import Drawer from '@/app/components/base/drawer'
|
||||||
import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import Loading from '@/app/components/base/loading'
|
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 { Button } from '@/app/components/base/ui/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
import { toast } from '@/app/components/base/ui/toast'
|
||||||
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
@ -401,15 +409,24 @@ const ProviderDetail = ({
|
|||||||
onSave={updateWorkflowToolProvider}
|
onSave={updateWorkflowToolProvider}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showConfirmDelete && (
|
<AlertDialog open={showConfirmDelete} onOpenChange={open => !open && setShowConfirmDelete(false)}>
|
||||||
<Confirm
|
<AlertDialogContent>
|
||||||
title={t('createTool.deleteToolConfirmTitle', { ns: 'tools' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t('createTool.deleteToolConfirmContent', { ns: 'tools' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
isShow={showConfirmDelete}
|
{t('createTool.deleteToolConfirmTitle', { ns: 'tools' })}
|
||||||
onConfirm={handleConfirmDelete}
|
</AlertDialogTitle>
|
||||||
onCancel={() => setShowConfirmDelete(false)}
|
<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>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -30,10 +30,10 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
|
|||||||
|
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
<div className="mb-6">
|
<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' })}
|
{t('onboarding.title', { ns: 'workflow' })}
|
||||||
</DialogTitle>
|
</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' })}
|
{t('onboarding.description', { ns: 'workflow' })}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +47,7 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
|
|||||||
|
|
||||||
{/* TODO: reduce z-1002 to match base/ui primitives after legacy overlay migration completes */}
|
{/* TODO: reduce z-1002 to match base/ui primitives after legacy overlay migration completes */}
|
||||||
<DialogPortal>
|
<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>
|
<span>{t('onboarding.escTip.press', { ns: 'workflow' })}</span>
|
||||||
<ShortcutsName keys={[t('onboarding.escTip.key', { ns: 'workflow' })]} textColor="secondary" />
|
<ShortcutsName keys={[t('onboarding.escTip.key', { ns: 'workflow' })]} textColor="secondary" />
|
||||||
<span>{t('onboarding.escTip.toDismiss', { ns: 'workflow' })}</span>
|
<span>{t('onboarding.escTip.toDismiss', { ns: 'workflow' })}</span>
|
||||||
|
|||||||
@ -408,4 +408,46 @@ describe('Workflow edge event wiring', () => {
|
|||||||
|
|
||||||
expect(store.getState().edgeMenu).toBeUndefined()
|
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,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
@ -34,9 +35,17 @@ import ReactFlow, {
|
|||||||
useReactFlow,
|
useReactFlow,
|
||||||
useStoreApi,
|
useStoreApi,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/app/components/base/ui/alert-dialog'
|
||||||
import { IS_DEV } from '@/config'
|
import { IS_DEV } from '@/config'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import dynamic from '@/next/dynamic'
|
|
||||||
import {
|
import {
|
||||||
useAllBuiltInTools,
|
useAllBuiltInTools,
|
||||||
useAllCustomTools,
|
useAllCustomTools,
|
||||||
@ -103,10 +112,6 @@ import { WorkflowHistoryProvider } from './workflow-history-store'
|
|||||||
import 'reactflow/dist/style.css'
|
import 'reactflow/dist/style.css'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
|
||||||
const Confirm = dynamic(() => import('@/app/components/base/confirm'), {
|
|
||||||
ssr: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
[CUSTOM_NODE]: CustomNode,
|
[CUSTOM_NODE]: CustomNode,
|
||||||
[CUSTOM_NOTE_NODE]: CustomNoteNode,
|
[CUSTOM_NOTE_NODE]: CustomNoteNode,
|
||||||
@ -133,6 +138,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
children,
|
children,
|
||||||
onWorkflowDataUpdate,
|
onWorkflowDataUpdate,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const workflowContainerRef = useRef<HTMLDivElement>(null)
|
const workflowContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const reactflow = useReactFlow()
|
const reactflow = useReactFlow()
|
||||||
@ -396,7 +402,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
<SyncingDataModal />
|
<SyncingDataModal />
|
||||||
<CandidateNode />
|
<CandidateNode />
|
||||||
<div
|
<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 }}
|
style={{ height: controlHeight }}
|
||||||
>
|
>
|
||||||
<Control />
|
<Control />
|
||||||
@ -407,17 +413,26 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
<EdgeContextmenu />
|
<EdgeContextmenu />
|
||||||
<SelectionContextmenu />
|
<SelectionContextmenu />
|
||||||
<HelpLine />
|
<HelpLine />
|
||||||
{
|
<AlertDialog open={!!showConfirm} onOpenChange={open => !open && setShowConfirm(undefined)}>
|
||||||
!!showConfirm && (
|
<AlertDialogContent>
|
||||||
<Confirm
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
isShow
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
onCancel={() => setShowConfirm(undefined)}
|
{showConfirm?.title}
|
||||||
onConfirm={showConfirm.onConfirm}
|
</AlertDialogTitle>
|
||||||
title={showConfirm.title}
|
{showConfirm?.desc && (
|
||||||
content={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}
|
{children}
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodeTypes={nodeTypes}
|
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 type { FC } from 'react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 = {
|
type Props = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@ -17,15 +25,30 @@ const RemoveVarConfirm: FC<Props> = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const title = t(`${i18nPrefix}.title`, { ns: 'workflow' })
|
||||||
|
const content = t(`${i18nPrefix}.content`, { ns: 'workflow' })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Confirm
|
<AlertDialog open={isShow} onOpenChange={open => !open && onCancel()}>
|
||||||
isShow={isShow}
|
<AlertDialogContent>
|
||||||
title={t(`${i18nPrefix}.title`, { ns: 'workflow' })}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
content={t(`${i18nPrefix}.content`, { ns: 'workflow' })}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
onConfirm={onConfirm}
|
{title}
|
||||||
onCancel={onCancel}
|
</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)
|
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/portal-to-follow-elem`
|
||||||
- `@/app/components/base/tooltip`
|
- `@/app/components/base/tooltip`
|
||||||
- `@/app/components/base/modal`
|
- `@/app/components/base/modal`
|
||||||
- `@/app/components/base/confirm`
|
|
||||||
- `@/app/components/base/select` (including `custom` / `pure`)
|
- `@/app/components/base/select` (including `custom` / `pure`)
|
||||||
- `@/app/components/base/popover`
|
- `@/app/components/base/popover`
|
||||||
- `@/app/components/base/dropdown`
|
- `@/app/components/base/dropdown`
|
||||||
|
|||||||
@ -132,7 +132,10 @@
|
|||||||
},
|
},
|
||||||
"app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx": {
|
"app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
|
},
|
||||||
|
"perfectionist/sort-imports": {
|
||||||
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx": {
|
"app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx": {
|
||||||
@ -281,7 +284,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/app-sidebar/dataset-info/dropdown.tsx": {
|
"app/components/app-sidebar/dataset-info/dropdown.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
},
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -334,14 +337,6 @@
|
|||||||
"count": 1
|
"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": {
|
"app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 7
|
"count": 7
|
||||||
@ -364,11 +359,6 @@
|
|||||||
"count": 2
|
"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": {
|
"app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx": {
|
||||||
"erasable-syntax-only/enums": {
|
"erasable-syntax-only/enums": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -381,9 +371,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/app/annotation/edit-annotation-modal/index.tsx": {
|
"app/components/app/annotation/edit-annotation-modal/index.tsx": {
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 2
|
"count": 2
|
||||||
}
|
}
|
||||||
@ -423,11 +410,6 @@
|
|||||||
"count": 8
|
"count": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/app/annotation/remove-annotation-confirm-modal/index.tsx": {
|
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app/components/app/annotation/type.ts": {
|
"app/components/app/annotation/type.ts": {
|
||||||
"erasable-syntax-only/enums": {
|
"erasable-syntax-only/enums": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@ -437,9 +419,6 @@
|
|||||||
"erasable-syntax-only/enums": {
|
"erasable-syntax-only/enums": {
|
||||||
"count": 1
|
"count": 1
|
||||||
},
|
},
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 5
|
"count": 5
|
||||||
},
|
},
|
||||||
@ -469,9 +448,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/app/app-publisher/features-wrapper.tsx": {
|
"app/components/app/app-publisher/features-wrapper.tsx": {
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 4
|
"count": 4
|
||||||
}
|
}
|
||||||
@ -586,9 +562,6 @@
|
|||||||
},
|
},
|
||||||
"app/components/app/configuration/config-var/index.tsx": {
|
"app/components/app/configuration/config-var/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -699,7 +672,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/app/configuration/config/automatic/get-automatic-res.tsx": {
|
"app/components/app/configuration/config/automatic/get-automatic-res.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
},
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 4
|
"count": 4
|
||||||
@ -762,7 +735,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/app/configuration/config/code-generator/get-code-generator-res.tsx": {
|
"app/components/app/configuration/config/code-generator/get-code-generator-res.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
},
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 4
|
"count": 4
|
||||||
@ -1085,7 +1058,10 @@
|
|||||||
},
|
},
|
||||||
"app/components/app/switch-app-modal/index.tsx": {
|
"app/components/app/switch-app-modal/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
|
},
|
||||||
|
"perfectionist/sort-imports": {
|
||||||
|
"count": 1
|
||||||
},
|
},
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -1387,21 +1363,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/base/chat/chat-with-history/header-in-mobile.tsx": {
|
"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": {
|
"ts/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/base/chat/chat-with-history/header/index.tsx": {
|
"app/components/base/chat/chat-with-history/header/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 1
|
"count": 1
|
||||||
},
|
},
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
@ -1448,7 +1415,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/base/chat/chat-with-history/sidebar/index.tsx": {
|
"app/components/base/chat/chat-with-history/sidebar/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"perfectionist/sort-imports": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1683,19 +1650,6 @@
|
|||||||
"count": 3
|
"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": {
|
"app/components/base/content-dialog/index.stories.tsx": {
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -3296,7 +3250,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/base/tag-management/tag-item-editor.tsx": {
|
"app/components/base/tag-management/tag-item-editor.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
},
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@ -3709,7 +3663,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/datasets/create-from-pipeline/list/template-card/index.tsx": {
|
"app/components/datasets/create-from-pipeline/list/template-card/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/datasets/create-from-pipeline/list/template-card/operations.tsx": {
|
"app/components/datasets/create-from-pipeline/list/template-card/operations.tsx": {
|
||||||
@ -3986,10 +3940,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/datasets/documents/components/operations.tsx": {
|
"app/components/datasets/documents/components/operations.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 3
|
"count": 2
|
||||||
},
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/datasets/documents/components/rename-modal.tsx": {
|
"app/components/datasets/documents/components/rename-modal.tsx": {
|
||||||
@ -4200,7 +4151,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/datasets/documents/detail/completed/common/batch-action.tsx": {
|
"app/components/datasets/documents/detail/completed/common/batch-action.tsx": {
|
||||||
"no-restricted-imports": {
|
"perfectionist/sort-imports": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -4293,10 +4244,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/datasets/documents/detail/completed/segment-card/index.tsx": {
|
"app/components/datasets/documents/detail/completed/segment-card/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 3
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/datasets/documents/detail/completed/segment-detail.tsx": {
|
"app/components/datasets/documents/detail/completed/segment-detail.tsx": {
|
||||||
@ -4426,20 +4374,12 @@
|
|||||||
},
|
},
|
||||||
"app/components/datasets/external-api/external-api-modal/index.tsx": {
|
"app/components/datasets/external-api/external-api-modal/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 3
|
"count": 2
|
||||||
},
|
},
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 1
|
"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": {
|
"app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": {
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -4586,11 +4526,6 @@
|
|||||||
"count": 9
|
"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": {
|
"app/components/datasets/list/dataset-card/components/description.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -4695,7 +4630,10 @@
|
|||||||
},
|
},
|
||||||
"app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx": {
|
"app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 3
|
"count": 2
|
||||||
|
},
|
||||||
|
"perfectionist/sort-imports": {
|
||||||
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/datasets/metadata/metadata-dataset/field.tsx": {
|
"app/components/datasets/metadata/metadata-dataset/field.tsx": {
|
||||||
@ -4864,7 +4802,10 @@
|
|||||||
},
|
},
|
||||||
"app/components/develop/secret-key/secret-key-modal.tsx": {
|
"app/components/develop/secret-key/secret-key-modal.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
|
},
|
||||||
|
"perfectionist/sort-imports": {
|
||||||
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/develop/tag.tsx": {
|
"app/components/develop/tag.tsx": {
|
||||||
@ -5094,11 +5035,6 @@
|
|||||||
"count": 2
|
"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": {
|
"app/components/header/account-setting/api-based-extension-page/modal.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -5118,12 +5054,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/header/account-setting/data-source-page-new/card.tsx": {
|
"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": {
|
"ts/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
}
|
}
|
||||||
@ -5298,7 +5228,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx": {
|
"app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 3
|
"count": 2
|
||||||
},
|
},
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@ -5502,7 +5432,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx": {
|
"app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
},
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -5821,7 +5751,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/plugins/plugin-auth/authorized/index.tsx": {
|
"app/components/plugins/plugin-auth/authorized/index.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 3
|
"count": 2
|
||||||
},
|
},
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@ -5954,10 +5884,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/plugins/plugin-detail-panel/endpoint-card.tsx": {
|
"app/components/plugins/plugin-detail-panel/endpoint-card.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 5
|
|
||||||
},
|
},
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@ -6075,11 +6002,6 @@
|
|||||||
"count": 1
|
"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": {
|
"app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx": {
|
||||||
"erasable-syntax-only/enums": {
|
"erasable-syntax-only/enums": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -6212,7 +6134,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/plugins/plugin-item/action.tsx": {
|
"app/components/plugins/plugin-item/action.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/plugins/plugin-item/index.tsx": {
|
"app/components/plugins/plugin-item/index.tsx": {
|
||||||
@ -6392,11 +6314,6 @@
|
|||||||
"count": 2
|
"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": {
|
"app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx": {
|
||||||
"react/component-hook-factories": {
|
"react/component-hook-factories": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -6542,11 +6459,6 @@
|
|||||||
"count": 1
|
"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": {
|
"app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -6760,7 +6672,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/tools/mcp/detail/content.tsx": {
|
"app/components/tools/mcp/detail/content.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
},
|
},
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 3
|
"count": 3
|
||||||
@ -6807,7 +6719,7 @@
|
|||||||
},
|
},
|
||||||
"app/components/tools/mcp/mcp-service-card.tsx": {
|
"app/components/tools/mcp/mcp-service-card.tsx": {
|
||||||
"no-restricted-imports": {
|
"no-restricted-imports": {
|
||||||
"count": 2
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/tools/mcp/modal.tsx": {
|
"app/components/tools/mcp/modal.tsx": {
|
||||||
@ -6816,15 +6728,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/tools/mcp/provider-card.tsx": {
|
"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": {
|
"ts/no-explicit-any": {
|
||||||
"count": 3
|
"count": 3
|
||||||
}
|
}
|
||||||
@ -6852,11 +6755,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/tools/provider/detail.tsx": {
|
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app/components/tools/provider/empty.tsx": {
|
"app/components/tools/provider/empty.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -6914,11 +6812,6 @@
|
|||||||
"count": 2
|
"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": {
|
"app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@ -7371,9 +7264,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app/components/workflow/index.tsx": {
|
"app/components/workflow/index.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"ts/no-explicit-any": {
|
"ts/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
}
|
}
|
||||||
@ -7724,11 +7614,6 @@
|
|||||||
"count": 1
|
"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": {
|
"app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx": {
|
||||||
"tailwindcss/enforce-consistent-class-order": {
|
"tailwindcss/enforce-consistent-class-order": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@ -10363,9 +10248,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hooks/use-pay.tsx": {
|
"hooks/use-pay.tsx": {
|
||||||
"no-restricted-imports": {
|
|
||||||
"count": 2
|
|
||||||
},
|
|
||||||
"react/set-state-in-effect": {
|
"react/set-state-in-effect": {
|
||||||
"count": 4
|
"count": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,13 +52,6 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [
|
|||||||
],
|
],
|
||||||
message: 'Deprecated: use @/app/components/base/ui/select instead. See issue #32767.',
|
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: [
|
group: [
|
||||||
'**/base/popover',
|
'**/base/popover',
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { IConfirm } from '@/app/components/base/confirm'
|
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { useRouter, useSearchParams } from '@/next/navigation'
|
||||||
import { useNotionBinding } from '@/service/use-common'
|
import { useNotionBinding } from '@/service/use-common'
|
||||||
|
|
||||||
type ConfirmType = Pick<IConfirm, 'type' | 'title' | 'content'>
|
type ConfirmType = {
|
||||||
|
type: 'info' | 'warning'
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
const useAnthropicCheckPay = () => {
|
const useAnthropicCheckPay = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -96,16 +105,32 @@ export const CheckModal = () => {
|
|||||||
if (!confirmInfo || !showPayStatusModal)
|
if (!confirmInfo || !showPayStatusModal)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
const description = (confirmInfo as { desc?: string }).desc || ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Confirm
|
<AlertDialog open={showPayStatusModal} onOpenChange={open => !open && handleCancelShowPayStatusModal()}>
|
||||||
isShow
|
<AlertDialogContent>
|
||||||
onCancel={handleCancelShowPayStatusModal}
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
onConfirm={handleCancelShowPayStatusModal}
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
showCancel={false}
|
{confirmInfo.title}
|
||||||
type={confirmInfo.type === 'info' ? 'info' : 'warning'}
|
</AlertDialogTitle>
|
||||||
title={confirmInfo.title}
|
{description && (
|
||||||
content={(confirmInfo as unknown as { desc: string }).desc || ''}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
confirmText={(confirmInfo.type === 'info' && t('operation.ok', { ns: 'common' })) || ''}
|
{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