import { memo, useCallback, useRef, useState, } from 'react' import { RiArrowDownSLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import type { PortalToFollowElemOptions, } from '@/app/components/base/portal-to-follow-elem' import Button from '@/app/components/base/button' import Indicator from '@/app/components/header/indicator' import cn from '@/utils/classnames' import Confirm from '@/app/components/base/confirm' import Authorize from '../authorize' import type { Credential } from '../types' import { CredentialTypeEnum } from '../types' import ApiKeyModal from '../authorize/api-key-modal' import Item from './item' import { useToastContext } from '@/app/components/base/toast' import type { PluginPayload } from '../types' import { useDeletePluginCredentialHook, useSetPluginDefaultCredentialHook, useUpdatePluginCredentialHook, } from '../hooks/use-credential' type AuthorizedProps = { pluginPayload: PluginPayload credentials: Credential[] canOAuth?: boolean canApiKey?: boolean disabled?: boolean renderTrigger?: (open?: boolean) => React.ReactNode isOpen?: boolean onOpenChange?: (open: boolean) => void offset?: PortalToFollowElemOptions['offset'] placement?: PortalToFollowElemOptions['placement'] triggerPopupSameWidth?: boolean popupClassName?: string disableSetDefault?: boolean onItemClick?: (id: string) => void extraAuthorizationItems?: Credential[] showItemSelectedIcon?: boolean selectedCredentialId?: string onUpdate?: () => void } const Authorized = ({ pluginPayload, credentials, canOAuth, canApiKey, disabled, renderTrigger, isOpen, onOpenChange, offset = 8, placement = 'bottom-start', triggerPopupSameWidth = true, popupClassName, disableSetDefault, onItemClick, extraAuthorizationItems, showItemSelectedIcon, selectedCredentialId, onUpdate, }: AuthorizedProps) => { const { t } = useTranslation() const { notify } = useToastContext() const [isLocalOpen, setIsLocalOpen] = useState(false) const mergedIsOpen = isOpen ?? isLocalOpen const setMergedIsOpen = useCallback((open: boolean) => { if (onOpenChange) onOpenChange(open) setIsLocalOpen(open) }, [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(null) const [deleteCredentialId, setDeleteCredentialId] = useState(null) const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload) const openConfirm = useCallback((credentialId?: string) => { if (credentialId) pendingOperationCredentialId.current = credentialId setDeleteCredentialId(pendingOperationCredentialId.current) }, []) const closeConfirm = useCallback(() => { setDeleteCredentialId(null) pendingOperationCredentialId.current = null }, []) const [doingAction, setDoingAction] = useState(false) const doingActionRef = useRef(doingAction) const handleSetDoingAction = useCallback((doing: boolean) => { doingActionRef.current = doing setDoingAction(doing) }, []) const handleConfirm = useCallback(async () => { if (doingActionRef.current) return if (!pendingOperationCredentialId.current) { setDeleteCredentialId(null) return } try { handleSetDoingAction(true) await deletePluginCredential({ credential_id: pendingOperationCredentialId.current }) notify({ type: 'success', message: t('common.api.actionSuccess'), }) onUpdate?.() setDeleteCredentialId(null) pendingOperationCredentialId.current = null } finally { handleSetDoingAction(false) } }, [deletePluginCredential, onUpdate, notify, t, handleSetDoingAction]) const [editValues, setEditValues] = useState | null>(null) const handleEdit = useCallback((id: string, values: Record) => { pendingOperationCredentialId.current = id setEditValues(values) }, []) const handleRemove = useCallback(() => { setDeleteCredentialId(pendingOperationCredentialId.current) }, []) const { mutateAsync: setPluginDefaultCredential } = useSetPluginDefaultCredentialHook(pluginPayload) const handleSetDefault = useCallback(async (id: string) => { if (doingActionRef.current) return try { handleSetDoingAction(true) await setPluginDefaultCredential(id) notify({ type: 'success', message: t('common.api.actionSuccess'), }) onUpdate?.() } finally { handleSetDoingAction(false) } }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) const handleRename = useCallback(async (payload: { credential_id: string name: string }) => { if (doingActionRef.current) return try { handleSetDoingAction(true) await updatePluginCredential(payload) notify({ type: 'success', message: t('common.api.actionSuccess'), }) onUpdate?.() } finally { handleSetDoingAction(false) } }, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate]) return ( <> setMergedIsOpen(!mergedIsOpen)} asChild > { renderTrigger ? renderTrigger(mergedIsOpen) : ( ) }
{ !!extraAuthorizationItems?.length && (
{ extraAuthorizationItems.map(credential => ( )) }
) } { !!oAuthCredentials.length && (
OAuth
{ oAuthCredentials.map(credential => ( )) }
) } { !!apiKeyCredentials.length && (
API Keys
{ apiKeyCredentials.map(credential => ( )) }
) }
{ deleteCredentialId && ( ) } { !!editValues && ( { setEditValues(null) pendingOperationCredentialId.current = null }} onRemove={handleRemove} disabled={disabled || doingAction} onUpdate={onUpdate} /> ) } ) } export default memo(Authorized)