Compare commits
4
Commits
lts/1.13.x
...
fix/ui-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b9938a72 | ||
|
|
de1c5149ad | ||
|
|
bdd6f952af | ||
|
|
5e2cb2fb12 |
@@ -8,7 +8,7 @@ API_ENV_EXAMPLE="$ROOT/api/.env.example"
|
||||
API_ENV="$ROOT/api/.env"
|
||||
WEB_ENV_EXAMPLE="$ROOT/web/.env.example"
|
||||
WEB_ENV="$ROOT/web/.env.local"
|
||||
MIDDLEWARE_ENV_EXAMPLE="$ROOT/docker/middleware.env.example"
|
||||
MIDDLEWARE_ENV_EXAMPLE="$ROOT/docker/envs/middleware.env.example"
|
||||
MIDDLEWARE_ENV="$ROOT/docker/middleware.env"
|
||||
|
||||
# 1) Copy api/.env.example -> api/.env
|
||||
@@ -17,7 +17,7 @@ cp "$API_ENV_EXAMPLE" "$API_ENV"
|
||||
# 2) Copy web/.env.example -> web/.env.local
|
||||
cp "$WEB_ENV_EXAMPLE" "$WEB_ENV"
|
||||
|
||||
# 3) Copy docker/middleware.env.example -> docker/middleware.env
|
||||
# 3) Copy docker/envs/middleware.env.example -> docker/middleware.env
|
||||
cp "$MIDDLEWARE_ENV_EXAMPLE" "$MIDDLEWARE_ENV"
|
||||
|
||||
# 4) Install deps
|
||||
|
||||
@@ -66,3 +66,6 @@ public/fallback-*.js
|
||||
.vscode/mcp.json
|
||||
|
||||
.eslintcache
|
||||
|
||||
# vinext
|
||||
.vinext
|
||||
|
||||
@@ -129,6 +129,9 @@ const defaultChatHookReturn: Partial<ChatHookReturn> = {
|
||||
suggestedQuestions: [],
|
||||
}
|
||||
|
||||
const getDisabledInputContainer = (textbox: HTMLElement) =>
|
||||
textbox.closest('.opacity-50')
|
||||
|
||||
describe('ChatWrapper', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@@ -320,7 +323,7 @@ describe('ChatWrapper', () => {
|
||||
render(<ChatWrapper />)
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const disabledContainer = chatInput.closest('.pointer-events-none')
|
||||
const disabledContainer = getDisabledInputContainer(chatInput)
|
||||
expect(disabledContainer).toBeInTheDocument()
|
||||
expect(disabledContainer).toHaveClass('opacity-50')
|
||||
})
|
||||
@@ -337,7 +340,7 @@ describe('ChatWrapper', () => {
|
||||
render(<ChatWrapper />)
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
expect(container).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -361,7 +364,7 @@ describe('ChatWrapper', () => {
|
||||
render(<ChatWrapper />)
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -384,7 +387,7 @@ describe('ChatWrapper', () => {
|
||||
|
||||
render(<ChatWrapper />)
|
||||
const textarea = screen.getByRole('textbox')
|
||||
const container = textarea.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(textarea)
|
||||
expect(container).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -411,7 +414,7 @@ describe('ChatWrapper', () => {
|
||||
render(<ChatWrapper />)
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -437,7 +440,7 @@ describe('ChatWrapper', () => {
|
||||
|
||||
render(<ChatWrapper />)
|
||||
const textarea = screen.getByRole('textbox')
|
||||
const container = textarea.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(textarea)
|
||||
expect(container).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -456,7 +459,7 @@ describe('ChatWrapper', () => {
|
||||
|
||||
render(<ChatWrapper />)
|
||||
const textarea = screen.getByRole('textbox')
|
||||
const container = textarea.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(textarea)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -472,7 +475,7 @@ describe('ChatWrapper', () => {
|
||||
|
||||
render(<ChatWrapper />)
|
||||
const textarea = screen.getByRole('textbox')
|
||||
const container = textarea.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(textarea)
|
||||
expect(container).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1101,7 +1104,7 @@ describe('ChatWrapper', () => {
|
||||
render(<ChatWrapper />)
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1242,7 +1245,7 @@ describe('ChatWrapper', () => {
|
||||
// This tests line 106 - early return when hasEmptyInput is set
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1270,7 +1273,7 @@ describe('ChatWrapper', () => {
|
||||
// This tests line 109 - early return when fileIsUploading is set
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1809,7 +1812,7 @@ describe('ChatWrapper', () => {
|
||||
render(<ChatWrapper />)
|
||||
const textboxes = screen.getAllByRole('textbox')
|
||||
const chatInput = textboxes[textboxes.length - 1]
|
||||
const container = chatInput.closest('.pointer-events-none')
|
||||
const container = getDisabledInputContainer(chatInput)
|
||||
// Should not be disabled because it's not required
|
||||
expect(container).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -938,6 +938,13 @@ describe('Chat', () => {
|
||||
expect(screen.getByTestId('chat-footer')).toHaveClass('bg-chat-input-mask')
|
||||
})
|
||||
|
||||
it('should let footer blank space pass pointer events through', () => {
|
||||
renderChat({ noChatInput: false })
|
||||
const footer = screen.getByTestId('chat-footer')
|
||||
expect(footer).toHaveClass('pointer-events-none')
|
||||
expect(footer.firstElementChild).toHaveClass('pointer-events-none')
|
||||
})
|
||||
|
||||
it('should apply chatFooterClassName when footer has content', () => {
|
||||
renderChat({
|
||||
noChatInput: false,
|
||||
@@ -954,6 +961,30 @@ describe('Chat', () => {
|
||||
const innerDivs = screen.getByTestId('chat-footer').querySelectorAll('div')
|
||||
expect(innerDivs.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should let footer whitespace pass pointer events through while preserving input interaction', () => {
|
||||
renderChat({
|
||||
noChatInput: false,
|
||||
})
|
||||
|
||||
const footer = screen.getByTestId('chat-footer')
|
||||
const footerInner = footer.firstElementChild as HTMLElement
|
||||
const inputWrapper = screen.getByTestId('chat-input-area').parentElement as HTMLElement
|
||||
|
||||
expect(footer).toHaveClass('pointer-events-none')
|
||||
expect(footerInner).toHaveClass('pointer-events-none')
|
||||
expect(inputWrapper).toHaveClass('pointer-events-auto')
|
||||
})
|
||||
|
||||
it('should keep stop responding action interactive inside the pass-through footer', () => {
|
||||
renderChat({
|
||||
isResponding: true,
|
||||
noStopResponding: false,
|
||||
noChatInput: true,
|
||||
})
|
||||
|
||||
expect(screen.getByTestId('stop-responding-container')).toHaveClass('pointer-events-auto')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Container and Spacing Variations', () => {
|
||||
|
||||
@@ -293,7 +293,7 @@ describe('ChatInputArea', () => {
|
||||
|
||||
it('should apply disabled styles when the disabled prop is true', () => {
|
||||
const { container } = render(<ChatInputArea visionConfig={mockVisionConfig} disabled />)
|
||||
expect(container.firstChild).toHaveClass('opacity-50')
|
||||
expect(container.firstChild).toHaveClass('pointer-events-none', 'opacity-50')
|
||||
})
|
||||
|
||||
it('should apply drag-active styles when a file is being dragged over', () => {
|
||||
|
||||
@@ -256,6 +256,7 @@ const Chat: FC<ChatProps> = ({
|
||||
}, [handleWindowResize, sidebarCollapseState])
|
||||
|
||||
const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend
|
||||
const shouldApplyFooterClassName = hasTryToAsk || !noChatInput || !noStopResponding
|
||||
|
||||
return (
|
||||
<ChatContextProvider
|
||||
@@ -327,16 +328,19 @@ const Chat: FC<ChatProps> = ({
|
||||
</div>
|
||||
<div
|
||||
data-testid="chat-footer"
|
||||
className={`absolute bottom-0 z-10 flex justify-center bg-chat-input-mask ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`}
|
||||
className={cn(
|
||||
'pointer-events-none absolute bottom-0 z-10 flex justify-center bg-chat-input-mask',
|
||||
shouldApplyFooterClassName && chatFooterClassName,
|
||||
)}
|
||||
ref={chatFooterRef}
|
||||
>
|
||||
<div
|
||||
ref={chatFooterInnerRef}
|
||||
className={cn('relative', chatFooterInnerClassName, isTryApp && 'px-0')}
|
||||
className={cn('pointer-events-none relative', chatFooterInnerClassName, isTryApp && 'px-0')}
|
||||
>
|
||||
{
|
||||
!noStopResponding && isResponding && (
|
||||
<div data-testid="stop-responding-container" className="mb-2 flex justify-center">
|
||||
<div data-testid="stop-responding-container" className="pointer-events-auto mb-2 flex justify-center">
|
||||
<Button className="border-components-panel-border bg-components-panel-bg text-components-button-secondary-text" onClick={onStopResponding}>
|
||||
<div className="i-custom-vender-solid-mediaAndDevices-stop-circle mr-[5px] h-3.5 w-3.5" />
|
||||
<span className="text-xs font-normal">{t('operation.stopResponding', { ns: 'appDebug' })}</span>
|
||||
@@ -346,31 +350,35 @@ const Chat: FC<ChatProps> = ({
|
||||
}
|
||||
{
|
||||
hasTryToAsk && (
|
||||
<TryToAsk
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
onSend={onSend}
|
||||
/>
|
||||
<div className="pointer-events-auto">
|
||||
<TryToAsk
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
onSend={onSend}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!noChatInput && (
|
||||
<ChatInputArea
|
||||
botName={appData?.site?.title || 'Bot'}
|
||||
disabled={inputDisabled}
|
||||
showFeatureBar={showFeatureBar}
|
||||
showFileUpload={showFileUpload}
|
||||
featureBarDisabled={isResponding}
|
||||
onFeatureBarClick={onFeatureBarClick}
|
||||
visionConfig={config?.file_upload}
|
||||
speechToTextConfig={config?.speech_to_text}
|
||||
onSend={onSend}
|
||||
inputs={inputs}
|
||||
inputsForm={inputsForm}
|
||||
theme={themeBuilder?.theme}
|
||||
isResponding={isResponding}
|
||||
readonly={readonly}
|
||||
sendOnEnter={sendOnEnter}
|
||||
/>
|
||||
<div className="pointer-events-auto">
|
||||
<ChatInputArea
|
||||
botName={appData?.site?.title || 'Bot'}
|
||||
disabled={inputDisabled}
|
||||
showFeatureBar={showFeatureBar}
|
||||
showFileUpload={showFileUpload}
|
||||
featureBarDisabled={isResponding}
|
||||
onFeatureBarClick={onFeatureBarClick}
|
||||
visionConfig={config?.file_upload}
|
||||
speechToTextConfig={config?.speech_to_text}
|
||||
onSend={onSend}
|
||||
inputs={inputs}
|
||||
inputsForm={inputsForm}
|
||||
theme={themeBuilder?.theme}
|
||||
isResponding={isResponding}
|
||||
readonly={readonly}
|
||||
sendOnEnter={sendOnEnter}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
+18
-9
@@ -13,10 +13,10 @@ import Configure from '../configure'
|
||||
|
||||
// Mock plugin auth components to isolate the unit test for Configure.
|
||||
vi.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
AddApiKeyButton: vi.fn(({ onUpdate, disabled, buttonText }: AddApiKeyButtonProps & { onUpdate: () => void }) => (
|
||||
AddApiKeyButton: vi.fn(({ onUpdate, disabled, buttonText }: AddApiKeyButtonProps) => (
|
||||
<button data-testid="add-api-key" onClick={onUpdate} disabled={disabled}>{buttonText}</button>
|
||||
)),
|
||||
AddOAuthButton: vi.fn(({ onUpdate, disabled, buttonText }: AddOAuthButtonProps & { onUpdate: () => void }) => (
|
||||
AddOAuthButton: vi.fn(({ onUpdate, disabled, buttonText }: AddOAuthButtonProps) => (
|
||||
<button data-testid="add-oauth" onClick={onUpdate} disabled={disabled}>{buttonText}</button>
|
||||
)),
|
||||
}))
|
||||
@@ -52,7 +52,7 @@ describe('Configure Component', () => {
|
||||
})
|
||||
|
||||
describe('Open State Management', () => {
|
||||
it('should toggle and manage the open state correctly', () => {
|
||||
it('should toggle and manage the open state correctly', async () => {
|
||||
// Arrange
|
||||
// Add a schema so we can detect if it's open by checking for button presence
|
||||
const itemWithApiKey: DataSourceAuth = {
|
||||
@@ -73,7 +73,9 @@ describe('Configure Component', () => {
|
||||
// Act: Click again to close
|
||||
fireEvent.click(trigger)
|
||||
// Assert: Now closed
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -134,7 +136,7 @@ describe('Configure Component', () => {
|
||||
})
|
||||
|
||||
describe('Update Handling', () => {
|
||||
it('should call onUpdate and close the portal when an update is triggered', () => {
|
||||
it('should call onUpdate and close the portal when an update is triggered', async () => {
|
||||
// Arrange
|
||||
const itemWithApiKey: DataSourceAuth = {
|
||||
...mockItemBase,
|
||||
@@ -148,10 +150,12 @@ describe('Configure Component', () => {
|
||||
|
||||
// Assert
|
||||
expect(mockOnUpdate).toHaveBeenCalledTimes(1)
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle missing onUpdate callback gracefully', () => {
|
||||
it('should handle missing onUpdate callback gracefully', async () => {
|
||||
// Arrange
|
||||
const itemWithBoth: DataSourceAuth = {
|
||||
...mockItemBase,
|
||||
@@ -165,11 +169,16 @@ describe('Configure Component', () => {
|
||||
// Act & Assert
|
||||
fireEvent.click(screen.getByRole('button', { name: /dataSource.configure/i }))
|
||||
fireEvent.click(screen.getByTestId('add-api-key'))
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /dataSource.configure/i }))
|
||||
fireEvent.click(screen.getByTestId('add-oauth'))
|
||||
expect(screen.queryByTestId('add-oauth')).not.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('add-api-key')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('add-oauth')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -8,7 +8,14 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import {
|
||||
ApiKeyModal,
|
||||
usePluginAuthAction,
|
||||
@@ -80,7 +87,7 @@ const Card = ({
|
||||
const handleAction = useCallback((
|
||||
action: string,
|
||||
credentialItem: DataSourceCredential,
|
||||
renamePayload?: Record<string, any>,
|
||||
renamePayload?: { credential_id: string, name: string },
|
||||
) => {
|
||||
if (action === 'edit') {
|
||||
handleEdit(
|
||||
@@ -98,19 +105,14 @@ const Card = ({
|
||||
if (action === 'setDefault')
|
||||
handleSetDefault(credentialItem.id)
|
||||
|
||||
if (action === 'rename')
|
||||
handleRename(renamePayload as any)
|
||||
if (action === 'rename' && renamePayload)
|
||||
handleRename(renamePayload)
|
||||
|
||||
if (action === 'change') {
|
||||
changeCredentialIdRef.current = credentialItem.id
|
||||
handleOAuth()
|
||||
}
|
||||
}, [
|
||||
openConfirm,
|
||||
handleEdit,
|
||||
handleSetDefault,
|
||||
handleRename,
|
||||
])
|
||||
}, [openConfirm, handleSetDefault, handleRename, handleEdit, handleOAuth])
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-background-section-burn">
|
||||
@@ -120,10 +122,10 @@ const Card = ({
|
||||
className="mr-3 flex h-10 w-10 shrink-0 items-center justify-center"
|
||||
/>
|
||||
<div className="grow">
|
||||
<div className="system-md-semibold text-text-primary">
|
||||
<div className="text-text-primary system-md-semibold">
|
||||
{renderI18nObject(label)}
|
||||
</div>
|
||||
<div className="system-xs-regular flex h-4 items-center text-text-tertiary">
|
||||
<div className="flex h-4 items-center text-text-tertiary system-xs-regular">
|
||||
{author}
|
||||
<div className="mx-0.5 text-text-quaternary">/</div>
|
||||
{name}
|
||||
@@ -135,7 +137,7 @@ const Card = ({
|
||||
onUpdate={handleAuthUpdate}
|
||||
/>
|
||||
</div>
|
||||
<div className="system-xs-medium flex h-4 items-center pl-3 text-text-tertiary">
|
||||
<div className="flex h-4 items-center pl-3 text-text-tertiary system-xs-medium">
|
||||
{t('auth.connectedWorkspace', { ns: 'plugin' })}
|
||||
<div className="ml-3 h-[1px] grow bg-divider-subtle"></div>
|
||||
</div>
|
||||
@@ -157,23 +159,30 @@ const Card = ({
|
||||
{
|
||||
!credentials_list.length && (
|
||||
<div className="p-3 pt-1">
|
||||
<div className="system-xs-regular flex h-10 items-center justify-center rounded-[10px] bg-background-section text-text-tertiary">
|
||||
<div className="flex h-10 items-center justify-center rounded-[10px] bg-background-section text-text-tertiary system-xs-regular">
|
||||
{t('auth.emptyAuth', { ns: 'plugin' })}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
deleteCredentialId && (
|
||||
<Confirm
|
||||
isShow
|
||||
title={t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
isDisabled={doingAction}
|
||||
onCancel={closeConfirm}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AlertDialog
|
||||
open={!!deleteCredentialId}
|
||||
onOpenChange={open => !open && closeConfirm()}
|
||||
>
|
||||
<AlertDialogContent backdropProps={{ forceRender: true }}>
|
||||
<div className="flex flex-col gap-2 px-6 pb-4 pt-6">
|
||||
<AlertDialogTitle className="w-full truncate text-text-primary title-2xl-semi-bold">
|
||||
{t('list.delete.title', { ns: 'datasetDocuments' })}
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton disabled={doingAction} onClick={handleConfirm}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{
|
||||
!!editValues && (
|
||||
<ApiKeyModal
|
||||
|
||||
@@ -4,9 +4,6 @@ import type {
|
||||
AddOAuthButtonProps,
|
||||
PluginPayload,
|
||||
} from '@/app/components/plugins/plugin-auth/types'
|
||||
import {
|
||||
RiAddLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
@@ -16,10 +13,10 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/app/components/base/ui/popover'
|
||||
import {
|
||||
AddApiKeyButton,
|
||||
AddOAuthButton,
|
||||
@@ -56,10 +53,6 @@ const Configure = ({
|
||||
}
|
||||
}, [pluginPayload, t])
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setOpen(v => !v)
|
||||
}, [])
|
||||
|
||||
const handleUpdate = useCallback(() => {
|
||||
setOpen(false)
|
||||
onUpdate?.()
|
||||
@@ -67,24 +60,27 @@ const Configure = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement="bottom-end"
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleToggle}>
|
||||
<Button
|
||||
variant="secondary-accent"
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
{t('dataSource.configure', { ns: 'common' })}
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-[1002]">
|
||||
<PopoverTrigger
|
||||
render={(
|
||||
<Button
|
||||
className="h-8"
|
||||
variant="secondary-accent"
|
||||
>
|
||||
<span className="i-ri-add-line size-4" />
|
||||
{t('dataSource.configure', { ns: 'common' })}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="bottom-end"
|
||||
sideOffset={4}
|
||||
alignOffset={-4}
|
||||
popupClassName="border-none bg-transparent shadow-none"
|
||||
>
|
||||
<div className="w-[240px] space-y-1.5 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-lg">
|
||||
{
|
||||
!!canOAuth && (
|
||||
@@ -122,8 +118,8 @@ const Configure = ({
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import Operator from './operator'
|
||||
|
||||
type ItemProps = {
|
||||
credentialItem: DataSourceCredential
|
||||
onAction: (action: string, credentialItem: DataSourceCredential, renamePayload?: Record<string, any>) => void
|
||||
onAction: (action: string, credentialItem: DataSourceCredential, renamePayload?: { credential_id: string, name: string }) => void
|
||||
}
|
||||
const Item = ({
|
||||
credentialItem,
|
||||
@@ -69,7 +69,7 @@ const Item = ({
|
||||
}
|
||||
{
|
||||
!renaming && (
|
||||
<div className="system-sm-medium grow text-text-secondary">
|
||||
<div className="grow text-text-secondary system-sm-medium">
|
||||
{credentialItem.name}
|
||||
</div>
|
||||
)
|
||||
@@ -78,7 +78,7 @@ const Item = ({
|
||||
<div className="mr-1 flex h-3 w-3 items-center justify-center">
|
||||
<Indicator color="green" />
|
||||
</div>
|
||||
<div className="system-xs-semibold-uppercase text-util-colors-green-green-600">
|
||||
<div className="text-util-colors-green-green-600 system-xs-semibold-uppercase">
|
||||
connected
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+21
-8
@@ -1,3 +1,4 @@
|
||||
import type { ApiKeyModalProps } from '../api-key-modal'
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
@@ -5,14 +6,16 @@ import AddApiKeyButton from '../add-api-key-button'
|
||||
|
||||
let _mockModalOpen = false
|
||||
vi.mock('../api-key-modal', () => ({
|
||||
default: ({ onClose, onUpdate }: { onClose: () => void, onUpdate?: () => void }) => {
|
||||
_mockModalOpen = true
|
||||
return (
|
||||
<div data-testid="api-key-modal">
|
||||
<button data-testid="modal-close" onClick={onClose}>Close</button>
|
||||
<button data-testid="modal-update" onClick={onUpdate}>Update</button>
|
||||
</div>
|
||||
)
|
||||
default: ({ open, onClose, onUpdate }: ApiKeyModalProps) => {
|
||||
_mockModalOpen = !!open
|
||||
return open
|
||||
? (
|
||||
<div data-testid="api-key-modal">
|
||||
<button data-testid="modal-close" onClick={onClose}>Close</button>
|
||||
<button data-testid="modal-update" onClick={onUpdate}>Update</button>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -47,6 +50,16 @@ describe('AddApiKeyButton', () => {
|
||||
expect(screen.getByTestId('api-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls custom onClick instead of mounting the inline modal', () => {
|
||||
const handleClick = vi.fn()
|
||||
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} onClick={handleClick} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
expect(handleClick).toHaveBeenCalledTimes(1)
|
||||
expect(screen.queryByTestId('api-key-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('respects disabled prop', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} disabled />)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import type { Ref } from 'react'
|
||||
import type { ApiKeyModalProps } from '../api-key-modal'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { useImperativeHandle } from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
|
||||
const mockNotify = vi.fn()
|
||||
const mockToastSuccess = vi.fn()
|
||||
const mockAddPluginCredential = vi.fn().mockResolvedValue({})
|
||||
const mockUpdatePluginCredential = vi.fn().mockResolvedValue({})
|
||||
const mockFormValues = { isCheckValidated: true, values: { __name__: 'My Key', api_key: 'sk-123' } }
|
||||
|
||||
vi.mock('@/app/components/base/toast/context', () => ({
|
||||
useToastContext: () => ({
|
||||
notify: mockNotify,
|
||||
}),
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
success: mockToastSuccess,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
@@ -42,36 +43,18 @@ vi.mock('@/app/components/base/encrypted-bottom', () => ({
|
||||
EncryptedBottom: () => <div data-testid="encrypted-bottom" />,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal/modal', () => ({
|
||||
default: ({ children, title, onClose, onConfirm, onExtraButtonClick, showExtraButton, disabled }: {
|
||||
children: React.ReactNode
|
||||
title: string
|
||||
onClose?: () => void
|
||||
onCancel?: () => void
|
||||
onConfirm?: () => void
|
||||
onExtraButtonClick?: () => void
|
||||
showExtraButton?: boolean
|
||||
disabled?: boolean
|
||||
[key: string]: unknown
|
||||
}) => (
|
||||
<div data-testid="modal">
|
||||
<div data-testid="modal-title">{title}</div>
|
||||
{children}
|
||||
<button data-testid="modal-confirm" onClick={onConfirm} disabled={disabled}>Confirm</button>
|
||||
<button data-testid="modal-close" onClick={onClose}>Close</button>
|
||||
{showExtraButton && <button data-testid="modal-extra" onClick={onExtraButtonClick}>Remove</button>}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({
|
||||
default: React.forwardRef((_props: Record<string, unknown>, ref: React.Ref<unknown>) => {
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
vi.mock('@/app/components/base/form/form-scenarios/auth', () => {
|
||||
const MockAuthForm = ({ ref }: Record<string, unknown> & { ref?: Ref<unknown> }) => {
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormValues: () => mockFormValues,
|
||||
}))
|
||||
return <div data-testid="auth-form" />
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
default: MockAuthForm,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/base/form/types', () => ({
|
||||
FormTypeEnum: { textInput: 'text-input' },
|
||||
@@ -106,20 +89,22 @@ describe('ApiKeyModal', () => {
|
||||
it('should show remove button when editValues is provided', () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} editValues={{ api_key: 'existing' }} />)
|
||||
|
||||
expect(screen.getByTestId('modal-extra')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.operation.remove')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show remove button in add mode', () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} />)
|
||||
|
||||
expect(screen.queryByTestId('modal-extra')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.operation.remove')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onClose when close button clicked', () => {
|
||||
it('should call close handlers when cancel button is clicked', () => {
|
||||
const mockOnClose = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} onClose={mockOnClose} />)
|
||||
const mockOnOpenChange = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} onClose={mockOnClose} onOpenChange={mockOnOpenChange} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-close'))
|
||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||
expect(mockOnOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -128,7 +113,7 @@ describe('ApiKeyModal', () => {
|
||||
const mockOnUpdate = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} onClose={mockOnClose} onUpdate={mockOnUpdate} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddPluginCredential).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -141,7 +126,7 @@ describe('ApiKeyModal', () => {
|
||||
it('should call updatePluginCredential on confirm in edit mode', async () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} editValues={{ api_key: 'existing', __credential_id__: 'cred-1' }} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdatePluginCredential).toHaveBeenCalled()
|
||||
@@ -152,7 +137,7 @@ describe('ApiKeyModal', () => {
|
||||
const mockOnRemove = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} editValues={{ api_key: 'existing' }} onRemove={mockOnRemove} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-extra'))
|
||||
fireEvent.click(screen.getByText('common.operation.remove'))
|
||||
expect(mockOnRemove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
+23
-36
@@ -1,18 +1,19 @@
|
||||
import type { Ref } from 'react'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { useImperativeHandle } from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
|
||||
const mockNotify = vi.fn()
|
||||
const mockToastSuccess = vi.fn()
|
||||
const mockSetPluginOAuthCustomClient = vi.fn().mockResolvedValue({})
|
||||
const mockDeletePluginOAuthCustomClient = vi.fn().mockResolvedValue({})
|
||||
const mockInvalidPluginOAuthClientSchema = vi.fn()
|
||||
const mockFormValues = { isCheckValidated: true, values: { __oauth_client__: 'custom', client_id: 'test-id' } }
|
||||
|
||||
vi.mock('@/app/components/base/toast/context', () => ({
|
||||
useToastContext: () => ({
|
||||
notify: mockNotify,
|
||||
}),
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
success: mockToastSuccess,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
@@ -33,36 +34,18 @@ vi.mock('../../../readme-panel/store', () => ({
|
||||
ReadmeShowType: { modal: 'modal' },
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal/modal', () => ({
|
||||
default: ({ children, title, onClose: _onClose, onConfirm, onCancel, onExtraButtonClick, footerSlot }: {
|
||||
children: React.ReactNode
|
||||
title: string
|
||||
onClose?: () => void
|
||||
onConfirm?: () => void
|
||||
onCancel?: () => void
|
||||
onExtraButtonClick?: () => void
|
||||
footerSlot?: React.ReactNode
|
||||
[key: string]: unknown
|
||||
}) => (
|
||||
<div data-testid="modal">
|
||||
<div data-testid="modal-title">{title}</div>
|
||||
{children}
|
||||
<button data-testid="modal-confirm" onClick={onConfirm}>Save And Auth</button>
|
||||
<button data-testid="modal-cancel" onClick={onCancel}>Save Only</button>
|
||||
<button data-testid="modal-close" onClick={onExtraButtonClick}>Cancel</button>
|
||||
{!!footerSlot && <div data-testid="footer-slot">{footerSlot}</div>}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({
|
||||
default: React.forwardRef((_props: Record<string, unknown>, ref: React.Ref<unknown>) => {
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
vi.mock('@/app/components/base/form/form-scenarios/auth', () => {
|
||||
const MockAuthForm = ({ ref }: Record<string, unknown> & { ref?: Ref<unknown> }) => {
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormValues: () => mockFormValues,
|
||||
}))
|
||||
return <div data-testid="auth-form" />
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
default: MockAuthForm,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-form', () => ({
|
||||
useForm: (config: Record<string, unknown>) => ({
|
||||
@@ -115,15 +98,18 @@ describe('OAuthClientSettings', () => {
|
||||
|
||||
it('should call onClose when cancel clicked', () => {
|
||||
const mockOnClose = vi.fn()
|
||||
const mockOnOpenChange = vi.fn()
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={basePayload}
|
||||
schemas={defaultSchemas}
|
||||
onClose={mockOnClose}
|
||||
onOpenChange={mockOnOpenChange}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-close'))
|
||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||
expect(mockOnOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -139,7 +125,7 @@ describe('OAuthClientSettings', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-cancel'))
|
||||
fireEvent.click(screen.getByText('plugin.auth.saveOnly'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetPluginOAuthCustomClient).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -158,11 +144,12 @@ describe('OAuthClientSettings', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'))
|
||||
fireEvent.click(screen.getByText('plugin.auth.saveAndAuth'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetPluginOAuthCustomClient).toHaveBeenCalled()
|
||||
})
|
||||
expect(mockOnAuth).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should render readme entrance when detail is provided', () => {
|
||||
|
||||
@@ -15,6 +15,13 @@ export type AddApiKeyButtonProps = {
|
||||
disabled?: boolean
|
||||
onUpdate?: () => void
|
||||
formSchemas?: FormSchema[]
|
||||
/**
|
||||
* If provided, clicking the button calls this callback instead of mounting
|
||||
* the modal inline. Use this when the button lives inside a Popover that
|
||||
* would unmount the modal on outside-click; the parent should render the
|
||||
* ApiKeyModal at a level above the Popover.
|
||||
*/
|
||||
onClick?: () => void
|
||||
}
|
||||
const AddApiKeyButton = ({
|
||||
pluginPayload,
|
||||
@@ -23,22 +30,31 @@ const AddApiKeyButton = ({
|
||||
disabled,
|
||||
onUpdate,
|
||||
formSchemas = [],
|
||||
onClick,
|
||||
}: AddApiKeyButtonProps) => {
|
||||
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||
const [isApiKeyModalMounted, setIsApiKeyModalMounted] = useState(false)
|
||||
const handleClick = onClick ?? (() => {
|
||||
setIsApiKeyModalMounted(true)
|
||||
setIsApiKeyModalOpen(true)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className="w-full"
|
||||
variant={buttonVariant}
|
||||
onClick={() => setIsApiKeyModalOpen(true)}
|
||||
onClick={handleClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
{
|
||||
isApiKeyModalOpen && (
|
||||
// Only mount the modal here when in uncontrolled mode (no onClick prop).
|
||||
!onClick && isApiKeyModalMounted && (
|
||||
<ApiKeyModal
|
||||
open={isApiKeyModalOpen}
|
||||
onOpenChange={setIsApiKeyModalOpen}
|
||||
pluginPayload={pluginPayload}
|
||||
onClose={() => setIsApiKeyModalOpen(false)}
|
||||
onUpdate={onUpdate}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import type { PluginPayload } from '../types'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
import {
|
||||
RiClipboardLine,
|
||||
RiEqualizer2Line,
|
||||
RiInformation2Fill,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
@@ -91,15 +86,15 @@ const AddOAuthButton = ({
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex rounded-xl bg-background-section-burn p-4">
|
||||
<div className="mr-3 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg">
|
||||
<RiInformation2Fill className="h-5 w-5 text-text-accent" />
|
||||
<span className="i-ri-information-2-fill h-5 w-5 text-text-accent" />
|
||||
</div>
|
||||
<div className="w-0 grow">
|
||||
<div className="system-sm-regular mb-1.5">
|
||||
<div className="mb-1.5 system-sm-regular">
|
||||
{t('auth.clientInfo', { ns: 'plugin' })}
|
||||
</div>
|
||||
{
|
||||
redirect_uri && (
|
||||
<div className="system-sm-medium flex w-full py-0.5">
|
||||
<div className="flex w-full py-0.5 system-sm-medium">
|
||||
<div className="w-0 grow break-words break-all">{redirect_uri}</div>
|
||||
<ActionButton
|
||||
className="shrink-0"
|
||||
@@ -107,14 +102,14 @@ const AddOAuthButton = ({
|
||||
navigator.clipboard.writeText(redirect_uri || '')
|
||||
}}
|
||||
>
|
||||
<RiClipboardLine className="h-4 w-4" />
|
||||
<span className="i-ri-clipboard-line h-4 w-4" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-sm-medium flex h-6 items-center text-text-secondary">
|
||||
<div className="flex h-6 items-center text-text-secondary system-sm-medium">
|
||||
{renderI18nObject(item.label as Record<string, string>)}
|
||||
{
|
||||
item.required && (
|
||||
@@ -180,7 +175,6 @@ const AddOAuthButton = ({
|
||||
return 'custom'
|
||||
}
|
||||
}, [isConfigured, is_oauth_custom_client_enabled, is_system_oauth_params_exists])
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
@@ -235,7 +229,7 @@ const AddOAuthButton = ({
|
||||
setIsOAuthSettingsOpen(true)
|
||||
}}
|
||||
>
|
||||
<RiEqualizer2Line className="h-4 w-4" />
|
||||
<span className="i-ri-equalizer-2-line h-4 w-4" />
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
@@ -248,7 +242,7 @@ const AddOAuthButton = ({
|
||||
disabled={disabled}
|
||||
className="w-full"
|
||||
>
|
||||
<RiEqualizer2Line className="mr-0.5 h-4 w-4" />
|
||||
<span className="i-ri-equalizer-2-line mr-0.5 h-4 w-4" />
|
||||
{t('auth.setupOAuth', { ns: 'plugin' })}
|
||||
</Button>
|
||||
)
|
||||
@@ -256,6 +250,8 @@ const AddOAuthButton = ({
|
||||
{
|
||||
isOAuthSettingsOpen && (
|
||||
<OAuthClientSettings
|
||||
open={isOAuthSettingsOpen}
|
||||
onOpenChange={setIsOAuthSettingsOpen}
|
||||
pluginPayload={pluginPayload}
|
||||
onClose={() => setIsOAuthSettingsOpen(false)}
|
||||
disabled={disabled || isLoading}
|
||||
|
||||
@@ -11,12 +11,13 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { EncryptedBottom } from '@/app/components/base/encrypted-bottom'
|
||||
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import { useToastContext } from '@/app/components/base/toast/context'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { ReadmeEntrance } from '../../readme-panel/entrance'
|
||||
import { ReadmeShowType } from '../../readme-panel/store'
|
||||
import {
|
||||
@@ -28,8 +29,10 @@ import { CredentialTypeEnum } from '../types'
|
||||
|
||||
export type ApiKeyModalProps = {
|
||||
pluginPayload: PluginPayload
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
onClose?: () => void
|
||||
editValues?: Record<string, any>
|
||||
editValues?: Record<string, unknown>
|
||||
onRemove?: () => void
|
||||
disabled?: boolean
|
||||
onUpdate?: () => void
|
||||
@@ -37,6 +40,8 @@ export type ApiKeyModalProps = {
|
||||
}
|
||||
const ApiKeyModal = ({
|
||||
pluginPayload,
|
||||
open = true,
|
||||
onOpenChange,
|
||||
onClose,
|
||||
editValues,
|
||||
onRemove,
|
||||
@@ -45,7 +50,6 @@ const ApiKeyModal = ({
|
||||
formSchemas: formSchemasFromProps = [],
|
||||
}: ApiKeyModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const [doingAction, setDoingAction] = useState(false)
|
||||
const doingActionRef = useRef(doingAction)
|
||||
const handleSetDoingAction = useCallback((value: boolean) => {
|
||||
@@ -74,7 +78,7 @@ const ApiKeyModal = ({
|
||||
if (schema.default)
|
||||
acc[schema.name] = schema.default
|
||||
return acc
|
||||
}, {} as Record<string, any>)
|
||||
}, {} as Record<string, unknown>)
|
||||
const { mutateAsync: addPluginCredential } = useAddPluginCredentialHook(pluginPayload)
|
||||
const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload)
|
||||
const formRef = useRef<FormRefObject>(null)
|
||||
@@ -113,58 +117,115 @@ const ApiKeyModal = ({
|
||||
name: __name__ || '',
|
||||
})
|
||||
}
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
toast.success(t('api.actionSuccess', { ns: 'common' }))
|
||||
|
||||
onOpenChange?.(false)
|
||||
onClose?.()
|
||||
onUpdate?.()
|
||||
}
|
||||
finally {
|
||||
handleSetDoingAction(false)
|
||||
}
|
||||
}, [addPluginCredential, onClose, onUpdate, updatePluginCredential, notify, t, editValues, handleSetDoingAction])
|
||||
}, [handleSetDoingAction, editValues, t, onOpenChange, onClose, onUpdate, updatePluginCredential, addPluginCredential])
|
||||
|
||||
const isDisabled = disabled || isLoading || doingAction
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
onOpenChange?.(false)
|
||||
onClose?.()
|
||||
}, [onClose, onOpenChange])
|
||||
|
||||
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
||||
onOpenChange?.(nextOpen)
|
||||
if (!nextOpen)
|
||||
onClose?.()
|
||||
}, [onClose, onOpenChange])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="md"
|
||||
title={t('auth.useApiAuth', { ns: 'plugin' })}
|
||||
subTitle={t('auth.useApiAuthDesc', { ns: 'plugin' })}
|
||||
onClose={onClose}
|
||||
onCancel={onClose}
|
||||
footerSlot={
|
||||
(<div></div>)
|
||||
}
|
||||
bottomSlot={<EncryptedBottom />}
|
||||
onConfirm={handleConfirm}
|
||||
showExtraButton={!!editValues}
|
||||
onExtraButtonClick={onRemove}
|
||||
disabled={disabled || isLoading || doingAction}
|
||||
clickOutsideNotClose={true}
|
||||
wrapperClassName="!z-[101]"
|
||||
<Dialog
|
||||
open={open}
|
||||
disablePointerDismissal
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
{pluginPayload.detail && (
|
||||
<ReadmeEntrance pluginDetail={pluginPayload.detail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
{
|
||||
isLoading && (
|
||||
<div className="flex h-40 items-center justify-center">
|
||||
<Loading />
|
||||
<DialogContent
|
||||
className="w-[640px] max-w-[calc(100vw-2rem)] p-0"
|
||||
backdropProps={{
|
||||
forceRender: true,
|
||||
}}
|
||||
>
|
||||
<div data-testid="modal" className="flex max-h-[80dvh] flex-col">
|
||||
<div className="relative shrink-0 p-6 pb-3 pr-14">
|
||||
<DialogTitle data-testid="modal-title" className="text-text-primary title-2xl-semi-bold">
|
||||
{t('auth.useApiAuth', { ns: 'plugin' })}
|
||||
</DialogTitle>
|
||||
<div className="mt-1 text-text-tertiary system-xs-regular">
|
||||
{t('auth.useApiAuthDesc', { ns: 'plugin' })}
|
||||
</div>
|
||||
<DialogCloseButton
|
||||
className="right-5 top-5 size-8 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isLoading && !!mergedData.length && (
|
||||
<AuthForm
|
||||
ref={formRef}
|
||||
formSchemas={formSchemas}
|
||||
defaultValues={editValues || defaultValues}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Modal>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-3">
|
||||
{pluginPayload.detail && (
|
||||
<ReadmeEntrance pluginDetail={pluginPayload.detail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
{
|
||||
isLoading && (
|
||||
<div className="flex h-40 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isLoading && !!mergedData.length && (
|
||||
<AuthForm
|
||||
ref={formRef}
|
||||
formSchemas={formSchemas}
|
||||
defaultValues={editValues || defaultValues}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex shrink-0 justify-between p-6 pt-5">
|
||||
<div />
|
||||
<div className="flex items-center">
|
||||
{
|
||||
!!editValues && (
|
||||
<>
|
||||
<Button
|
||||
variant="warning"
|
||||
onClick={onRemove}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('operation.remove', { ns: 'common' })}
|
||||
</Button>
|
||||
<div className="mx-3 h-4 w-[1px] bg-divider-regular"></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-2"
|
||||
variant="primary"
|
||||
onClick={handleConfirm}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<EncryptedBottom />
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import { useToastContext } from '@/app/components/base/toast/context'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { ReadmeEntrance } from '../../readme-panel/entrance'
|
||||
import { ReadmeShowType } from '../../readme-panel/store'
|
||||
import {
|
||||
@@ -28,8 +28,10 @@ import {
|
||||
|
||||
type OAuthClientSettingsProps = {
|
||||
pluginPayload: PluginPayload
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
onClose?: () => void
|
||||
editValues?: Record<string, any>
|
||||
editValues?: Record<string, unknown>
|
||||
disabled?: boolean
|
||||
schemas: FormSchema[]
|
||||
onAuth?: () => Promise<void>
|
||||
@@ -38,6 +40,8 @@ type OAuthClientSettingsProps = {
|
||||
}
|
||||
const OAuthClientSettings = ({
|
||||
pluginPayload,
|
||||
open = true,
|
||||
onOpenChange,
|
||||
onClose,
|
||||
editValues,
|
||||
disabled,
|
||||
@@ -47,18 +51,26 @@ const OAuthClientSettings = ({
|
||||
onUpdate,
|
||||
}: OAuthClientSettingsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const [doingAction, setDoingAction] = useState(false)
|
||||
const doingActionRef = useRef(doingAction)
|
||||
const handleSetDoingAction = useCallback((value: boolean) => {
|
||||
doingActionRef.current = value
|
||||
setDoingAction(value)
|
||||
}, [])
|
||||
const handleClose = useCallback(() => {
|
||||
onOpenChange?.(false)
|
||||
onClose?.()
|
||||
}, [onClose, onOpenChange])
|
||||
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
||||
onOpenChange?.(nextOpen)
|
||||
if (!nextOpen)
|
||||
onClose?.()
|
||||
}, [onClose, onOpenChange])
|
||||
const defaultValues = schemas.reduce((acc, schema) => {
|
||||
if (schema.default)
|
||||
acc[schema.name] = schema.default
|
||||
return acc
|
||||
}, {} as Record<string, any>)
|
||||
}, {} as Record<string, unknown>)
|
||||
const { mutateAsync: setPluginOAuthCustomClient } = useSetPluginOAuthCustomClientHook(pluginPayload)
|
||||
const invalidPluginOAuthClientSchema = useInvalidPluginOAuthClientSchemaHook(pluginPayload)
|
||||
const formRef = useRef<FormRefObject>(null)
|
||||
@@ -86,11 +98,9 @@ const OAuthClientSettings = ({
|
||||
client_params: restValues,
|
||||
enable_oauth_custom_client: __oauth_client__ === 'custom',
|
||||
})
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
toast.success(t('api.actionSuccess', { ns: 'common' }))
|
||||
|
||||
onOpenChange?.(false)
|
||||
onClose?.()
|
||||
onUpdate?.()
|
||||
invalidPluginOAuthClientSchema()
|
||||
@@ -98,7 +108,7 @@ const OAuthClientSettings = ({
|
||||
finally {
|
||||
handleSetDoingAction(false)
|
||||
}
|
||||
}, [onClose, onUpdate, invalidPluginOAuthClientSchema, setPluginOAuthCustomClient, notify, t, handleSetDoingAction])
|
||||
}, [handleSetDoingAction, setPluginOAuthCustomClient, t, onOpenChange, onClose, onUpdate, invalidPluginOAuthClientSchema])
|
||||
|
||||
const handleConfirmAndAuthorize = useCallback(async () => {
|
||||
await handleConfirm()
|
||||
@@ -113,10 +123,8 @@ const OAuthClientSettings = ({
|
||||
try {
|
||||
handleSetDoingAction(true)
|
||||
await deletePluginOAuthCustomClient()
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
toast.success(t('api.actionSuccess', { ns: 'common' }))
|
||||
onOpenChange?.(false)
|
||||
onClose?.()
|
||||
onUpdate?.()
|
||||
invalidPluginOAuthClientSchema()
|
||||
@@ -124,53 +132,89 @@ const OAuthClientSettings = ({
|
||||
finally {
|
||||
handleSetDoingAction(false)
|
||||
}
|
||||
}, [onUpdate, invalidPluginOAuthClientSchema, deletePluginOAuthCustomClient, notify, t, handleSetDoingAction, onClose])
|
||||
}, [handleSetDoingAction, deletePluginOAuthCustomClient, t, onOpenChange, onClose, onUpdate, invalidPluginOAuthClientSchema])
|
||||
const form = useForm({
|
||||
defaultValues: editValues || defaultValues,
|
||||
})
|
||||
const __oauth_client__ = useStore(form.store, s => s.values.__oauth_client__)
|
||||
const isDisabled = disabled || doingAction
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('auth.oauthClientSettings', { ns: 'plugin' })}
|
||||
confirmButtonText={t('auth.saveAndAuth', { ns: 'plugin' })}
|
||||
cancelButtonText={t('auth.saveOnly', { ns: 'plugin' })}
|
||||
extraButtonText={t('operation.cancel', { ns: 'common' })}
|
||||
showExtraButton
|
||||
extraButtonVariant="secondary"
|
||||
onExtraButtonClick={onClose}
|
||||
onClose={onClose}
|
||||
onCancel={handleConfirm}
|
||||
onConfirm={handleConfirmAndAuthorize}
|
||||
disabled={disabled || doingAction}
|
||||
footerSlot={
|
||||
__oauth_client__ === 'custom' && hasOriginalClientParams && (
|
||||
<div className="grow">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="text-components-button-destructive-secondary-text"
|
||||
disabled={disabled || doingAction || !editValues}
|
||||
onClick={handleRemove}
|
||||
>
|
||||
{t('operation.remove', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
containerClassName="pt-0"
|
||||
wrapperClassName="!z-[101]"
|
||||
clickOutsideNotClose={true}
|
||||
<Dialog
|
||||
open={open}
|
||||
disablePointerDismissal
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
{pluginPayload.detail && (
|
||||
<ReadmeEntrance pluginDetail={pluginPayload.detail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
<AuthForm
|
||||
formFromProps={form}
|
||||
ref={formRef}
|
||||
formSchemas={schemas}
|
||||
defaultValues={editValues || defaultValues}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Modal>
|
||||
<DialogContent
|
||||
backdropProps={{ forceRender: true }}
|
||||
className="w-[480px] max-w-[calc(100vw-2rem)] p-0"
|
||||
>
|
||||
<div data-testid="modal" className="flex max-h-[80dvh] flex-col">
|
||||
<div className="relative shrink-0 p-6 pb-3 pr-14">
|
||||
<DialogTitle data-testid="modal-title" className="text-text-primary title-2xl-semi-bold">
|
||||
{t('auth.oauthClientSettings', { ns: 'plugin' })}
|
||||
</DialogTitle>
|
||||
<DialogCloseButton
|
||||
className="right-5 top-5 size-8 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-3 pt-0">
|
||||
{pluginPayload.detail && (
|
||||
<ReadmeEntrance pluginDetail={pluginPayload.detail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
<AuthForm
|
||||
formFromProps={form}
|
||||
ref={formRef}
|
||||
formSchemas={schemas}
|
||||
defaultValues={editValues || defaultValues}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex shrink-0 justify-between p-6 pt-5">
|
||||
<div>
|
||||
{
|
||||
__oauth_client__ === 'custom' && hasOriginalClientParams && (
|
||||
<div className="grow">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="text-components-button-destructive-secondary-text"
|
||||
disabled={isDisabled || !editValues}
|
||||
onClick={handleRemove}
|
||||
>
|
||||
{t('operation.remove', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleClose}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<div className="mx-3 h-4 w-[1px] bg-divider-regular"></div>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('auth.saveOnly', { ns: 'plugin' })}
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-2"
|
||||
variant="primary"
|
||||
onClick={handleConfirmAndAuthorize}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('auth.saveAndAuth', { ns: 'plugin' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@ import type { Credential, PluginPayload } from '../types'
|
||||
import type {
|
||||
PortalToFollowElemOptions,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
@@ -86,18 +83,18 @@ const Authorized = ({
|
||||
}, [onOpenChange])
|
||||
const oAuthCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.OAUTH2)
|
||||
const apiKeyCredentials = credentials.filter(credential => credential.credential_type === CredentialTypeEnum.API_KEY)
|
||||
const pendingOperationCredentialId = useRef<string | null>(null)
|
||||
const pendingOperationCredentialIdRef = useRef<string | null>(null)
|
||||
const [deleteCredentialId, setDeleteCredentialId] = useState<string | null>(null)
|
||||
const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload)
|
||||
const openConfirm = useCallback((credentialId?: string) => {
|
||||
if (credentialId)
|
||||
pendingOperationCredentialId.current = credentialId
|
||||
pendingOperationCredentialIdRef.current = credentialId
|
||||
|
||||
setDeleteCredentialId(pendingOperationCredentialId.current)
|
||||
setDeleteCredentialId(pendingOperationCredentialIdRef.current)
|
||||
}, [])
|
||||
const closeConfirm = useCallback(() => {
|
||||
setDeleteCredentialId(null)
|
||||
pendingOperationCredentialId.current = null
|
||||
pendingOperationCredentialIdRef.current = null
|
||||
}, [])
|
||||
const [doingAction, setDoingAction] = useState(false)
|
||||
const doingActionRef = useRef(doingAction)
|
||||
@@ -108,32 +105,41 @@ const Authorized = ({
|
||||
const handleConfirm = useCallback(async () => {
|
||||
if (doingActionRef.current)
|
||||
return
|
||||
if (!pendingOperationCredentialId.current) {
|
||||
if (!pendingOperationCredentialIdRef.current) {
|
||||
setDeleteCredentialId(null)
|
||||
return
|
||||
}
|
||||
try {
|
||||
handleSetDoingAction(true)
|
||||
await deletePluginCredential({ credential_id: pendingOperationCredentialId.current })
|
||||
await deletePluginCredential({ credential_id: pendingOperationCredentialIdRef.current })
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
onUpdate?.()
|
||||
setDeleteCredentialId(null)
|
||||
pendingOperationCredentialId.current = null
|
||||
pendingOperationCredentialIdRef.current = null
|
||||
}
|
||||
finally {
|
||||
handleSetDoingAction(false)
|
||||
}
|
||||
}, [deletePluginCredential, onUpdate, notify, t, handleSetDoingAction])
|
||||
const [editValues, setEditValues] = useState<Record<string, any> | null>(null)
|
||||
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||
const handleEdit = useCallback((id: string, values: Record<string, any>) => {
|
||||
pendingOperationCredentialId.current = id
|
||||
pendingOperationCredentialIdRef.current = id
|
||||
setEditValues(values)
|
||||
setIsApiKeyModalOpen(true)
|
||||
}, [])
|
||||
const handleApiKeyModalOpenChange = useCallback((open: boolean) => {
|
||||
setIsApiKeyModalOpen(open)
|
||||
if (!open) {
|
||||
setEditValues(null)
|
||||
pendingOperationCredentialIdRef.current = null
|
||||
}
|
||||
}, [])
|
||||
const handleRemove = useCallback(() => {
|
||||
setDeleteCredentialId(pendingOperationCredentialId.current)
|
||||
setDeleteCredentialId(pendingOperationCredentialIdRef.current)
|
||||
}, [])
|
||||
const { mutateAsync: setPluginDefaultCredential } = useSetPluginDefaultCredentialHook(pluginPayload)
|
||||
const handleSetDefault = useCallback(async (id: string) => {
|
||||
@@ -211,7 +217,7 @@ const Authorized = ({
|
||||
` (${unavailableCredentials.length} ${t('auth.unavailable', { ns: 'plugin' })})`
|
||||
)
|
||||
}
|
||||
<RiArrowDownSLine className="ml-0.5 h-4 w-4" />
|
||||
<span className="i-ri-arrow-down-s-line ml-0.5 h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -342,12 +348,11 @@ const Authorized = ({
|
||||
{
|
||||
!!editValues && (
|
||||
<ApiKeyModal
|
||||
open={isApiKeyModalOpen}
|
||||
onOpenChange={handleApiKeyModalOpenChange}
|
||||
pluginPayload={pluginPayload}
|
||||
editValues={editValues}
|
||||
onClose={() => {
|
||||
setEditValues(null)
|
||||
pendingOperationCredentialId.current = null
|
||||
}}
|
||||
onClose={() => handleApiKeyModalOpenChange(false)}
|
||||
onRemove={handleRemove}
|
||||
disabled={disabled || doingAction}
|
||||
onUpdate={onUpdate}
|
||||
|
||||
@@ -4661,22 +4661,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/data-source-page-new/card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 4
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/data-source-page-new/configure.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/data-source-page-new/hooks/use-marketplace-all-plugins.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -4690,14 +4674,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/data-source-page-new/item.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/data-source-page-new/operator.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
@@ -5190,17 +5166,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/authorize/api-key-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -5213,14 +5178,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/authorize/oauth-client-settings.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/authorized-in-node.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
|
||||
Reference in New Issue
Block a user