refactor(sandbox-provider): extract shared constants and remove redundant cache invalidation

- Extract PROVIDER_ICONS and PROVIDER_DESCRIPTION_KEYS to constants.ts
- Create shared ProviderIcon component with size and withBorder props
- Remove manual invalidateList() calls from config-modal and switch-modal
  (mutations already invalidate cache in onSuccess)
- Remove unused useInvalidSandboxProviderList hook
This commit is contained in:
yyh 2026-01-13 16:18:08 +08:00
parent ffc39b0235
commit 48295e5161
No known key found for this signature in database
6 changed files with 57 additions and 65 deletions

View File

@ -12,44 +12,22 @@ import Modal from '@/app/components/base/modal'
import { useToastContext } from '@/app/components/base/toast'
import {
useDeleteSandboxProviderConfig,
useInvalidSandboxProviderList,
useSaveSandboxProviderConfig,
} from '@/service/use-sandbox-provider'
import { PROVIDER_DOC_LINKS, SANDBOX_FIELD_CONFIGS } from './constants'
import ProviderIcon from './provider-icon'
type ConfigModalProps = {
provider: SandboxProvider
onClose: () => void
}
const PROVIDER_ICONS: Record<string, string> = {
e2b: '/sandbox-providers/e2b.svg',
daytona: '/sandbox-providers/daytona.svg',
docker: '/sandbox-providers/docker.svg',
local: '/sandbox-providers/local.svg',
}
const ProviderIcon = ({ providerType }: { providerType: string }) => {
const iconSrc = PROVIDER_ICONS[providerType] || PROVIDER_ICONS.e2b
return (
<div className="h-4 w-4 shrink-0 text-clip rounded border-[0.5px] border-divider-subtle">
<img
src={iconSrc}
alt={`${providerType} icon`}
className="h-full w-full object-cover"
/>
</div>
)
}
const ConfigModal = ({
provider,
onClose,
}: ConfigModalProps) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const invalidateList = useInvalidSandboxProviderList()
const formRef = useRef<FormRefObject>(null)
const { mutateAsync: saveConfig, isPending: isSaving } = useSaveSandboxProviderConfig()
@ -84,26 +62,24 @@ const ConfigModal = ({
providerType: provider.provider_type,
config: formValues.values,
})
await invalidateList()
notify({ type: 'success', message: t('api.saved', { ns: 'common' }) })
onClose()
}
catch {
// Error toast is handled by fetch layer
}
}, [saveConfig, provider.provider_type, invalidateList, notify, t, onClose])
}, [saveConfig, provider.provider_type, notify, t, onClose])
const handleRevoke = useCallback(async () => {
try {
await deleteConfig(provider.provider_type)
await invalidateList()
notify({ type: 'success', message: t('api.remove', { ns: 'common' }) })
onClose()
}
catch {
// Error toast is handled by fetch layer
}
}, [deleteConfig, provider.provider_type, invalidateList, notify, t, onClose])
}, [deleteConfig, provider.provider_type, notify, t, onClose])
const isConfigured = provider.is_tenant_configured
const docLink = PROVIDER_DOC_LINKS[provider.provider_type]
@ -121,7 +97,7 @@ const ConfigModal = ({
{t('sandboxProvider.configModal.title', { ns: 'common' })}
</h3>
<div className="flex items-center gap-2">
<ProviderIcon providerType={provider.provider_type} />
<ProviderIcon providerType={provider.provider_type} size="sm" withBorder />
<span className="system-md-regular text-text-secondary">{provider.label}</span>
</div>
</div>

View File

@ -1,5 +1,19 @@
import { FormTypeEnum } from '@/app/components/base/form/types'
export const PROVIDER_ICONS: Record<string, string> = {
e2b: '/sandbox-providers/e2b.svg',
daytona: '/sandbox-providers/daytona.svg',
docker: '/sandbox-providers/docker.svg',
local: '/sandbox-providers/local.svg',
}
export const PROVIDER_DESCRIPTION_KEYS = {
e2b: 'sandboxProvider.e2b.description',
daytona: 'sandboxProvider.daytona.description',
docker: 'sandboxProvider.docker.description',
local: 'sandboxProvider.local.description',
} as const
export const SANDBOX_FIELD_CONFIGS = {
api_key: {
labelKey: 'sandboxProvider.configModal.apiKey',

View File

@ -7,6 +7,8 @@ import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator'
import { cn } from '@/utils/classnames'
import { PROVIDER_DESCRIPTION_KEYS } from './constants'
import ProviderIcon from './provider-icon'
type ProviderCardProps = {
provider: SandboxProvider
@ -16,31 +18,6 @@ type ProviderCardProps = {
disabled?: boolean
}
const PROVIDER_ICONS: Record<string, string> = {
e2b: '/sandbox-providers/e2b.svg',
daytona: '/sandbox-providers/daytona.svg',
docker: '/sandbox-providers/docker.svg',
local: '/sandbox-providers/local.svg',
}
const PROVIDER_DESCRIPTION_KEYS = {
e2b: 'sandboxProvider.e2b.description',
daytona: 'sandboxProvider.daytona.description',
docker: 'sandboxProvider.docker.description',
local: 'sandboxProvider.local.description',
} as const
const ProviderIcon = ({ providerType }: { providerType: string }) => {
const iconSrc = PROVIDER_ICONS[providerType] || PROVIDER_ICONS.e2b
return (
<img
src={iconSrc}
alt={`${providerType} icon`}
className="h-6 w-6"
/>
)
}
const ProviderCard = ({
provider,
isCurrent = false,

View File

@ -0,0 +1,35 @@
import { cn } from '@/utils/classnames'
import { PROVIDER_ICONS } from './constants'
type ProviderIconProps = {
providerType: string
size?: 'sm' | 'md'
withBorder?: boolean
}
const ProviderIcon = ({ providerType, size = 'md', withBorder = false }: ProviderIconProps) => {
const iconSrc = PROVIDER_ICONS[providerType] || PROVIDER_ICONS.e2b
const sizeClass = size === 'sm' ? 'h-4 w-4' : 'h-6 w-6'
if (withBorder) {
return (
<div className={cn('shrink-0 text-clip rounded border-[0.5px] border-divider-subtle', sizeClass)}>
<img
src={iconSrc}
alt={`${providerType} icon`}
className="h-full w-full object-cover"
/>
</div>
)
}
return (
<img
src={iconSrc}
alt={`${providerType} icon`}
className={sizeClass}
/>
)
}
export default ProviderIcon

View File

@ -6,10 +6,7 @@ import { Trans, useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal'
import { useToastContext } from '@/app/components/base/toast'
import {
useActivateSandboxProvider,
useInvalidSandboxProviderList,
} from '@/service/use-sandbox-provider'
import { useActivateSandboxProvider } from '@/service/use-sandbox-provider'
type SwitchModalProps = {
provider: SandboxProvider
@ -22,21 +19,19 @@ const SwitchModal = ({
}: SwitchModalProps) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const invalidateList = useInvalidSandboxProviderList()
const { mutateAsync: activateProvider, isPending } = useActivateSandboxProvider()
const handleConfirm = useCallback(async () => {
try {
await activateProvider(provider.provider_type)
await invalidateList()
notify({ type: 'success', message: t('api.success', { ns: 'common' }) })
onClose()
}
catch {
// Error toast is handled by fetch layer
}
}, [activateProvider, provider.provider_type, invalidateList, notify, t, onClose])
}, [activateProvider, provider.provider_type, notify, t, onClose])
return (
<Modal

View File

@ -4,7 +4,6 @@ import {
useQueryClient,
} from '@tanstack/react-query'
import { del, get, post } from './base'
import { useInvalid } from './use-base'
const NAME_SPACE = 'sandbox-provider'
@ -40,10 +39,6 @@ export const useGetSandboxProviderList = () => {
})
}
export const useInvalidSandboxProviderList = () => {
return useInvalid(sandboxProviderQueryKeys.list)
}
export const useGetSandboxProvider = (providerType: string) => {
return useQuery({
queryKey: sandboxProviderQueryKeys.provider(providerType),