import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AlertCircle, CheckCircle2, X } from 'lucide-react'; import { useI18n } from '../../i18n'; import type { ChannelConfigFieldMeta, ChannelConfigFieldValueMap } from '../../lib/channel-types'; import { getChannelMeta, type ChannelMeta } from '../../lib/channel-meta'; import ChannelConfigActions from './ChannelConfigActions'; import ChannelConfigFields from './ChannelConfigFields'; import ChannelInstructionsPanel from './ChannelInstructionsPanel'; export type ChannelFieldValidationResult = { key: string; label: string; kind: string; required: boolean; provided: boolean; valid: boolean; errors: string[]; warnings: string[]; }; export type ChannelCredentialsValidationResult = { valid: boolean; errors: string[]; warnings: string[]; details?: { channelType?: string; accountId?: string; channelName?: string; connectionType?: string; fields?: ChannelFieldValidationResult[]; [key: string]: unknown; }; }; export type ChannelConfigModalProps = { open: boolean; selectedChannelType: string; values: ChannelConfigFieldValueMap; onValueChange: (key: string, value: string) => void; onClose: () => void; onConfirm: () => void | Promise; onValidate?: () => void | Promise; error?: string | null; submitting?: boolean; validating?: boolean; validationResult?: ChannelCredentialsValidationResult | null; title?: string; description?: string; confirmLabel?: string; validateLabel?: string; validatingLabel?: string; }; function withTranslatedMeta( meta: ChannelMeta, translate: (path: string, fallback: string) => string, ): ChannelMeta { return { ...meta, name: translate(`channels.meta.${meta.type}.name`, meta.name), description: translate(`channels.meta.${meta.type}.description`, meta.description), docsUrl: meta.docsUrl ? translate(`channels.meta.${meta.type}.docsUrl`, meta.docsUrl) : meta.docsUrl, instructions: meta.instructions.map((instruction, index) => ( translate(`channels.meta.${meta.type}.instructions.${index}`, instruction) )), configFields: meta.configFields.map((field) => withTranslatedField(meta.type, field, translate)), }; } function withTranslatedField( channelType: string, field: ChannelConfigFieldMeta, translate: (path: string, fallback: string) => string, ): ChannelConfigFieldMeta { return { ...field, label: translate(`channels.meta.${channelType}.fields.${field.key}.label`, field.label), placeholder: field.placeholder ? translate(`channels.meta.${channelType}.fields.${field.key}.placeholder`, field.placeholder) : field.placeholder, description: field.description ? translate(`channels.meta.${channelType}.fields.${field.key}.description`, field.description) : field.description, docsUrl: field.docsUrl ? translate(`channels.meta.${channelType}.fields.${field.key}.docsUrl`, field.docsUrl) : field.docsUrl, }; } export default function ChannelConfigModal({ open, selectedChannelType, values, onValueChange, onClose, onConfirm, onValidate, error, submitting, validating, validationResult, title, description, confirmLabel, validateLabel, validatingLabel, }: ChannelConfigModalProps) { const { t, hasMessage } = useI18n(); const [showSecrets, setShowSecrets] = useState>({}); const firstFieldRef = useRef(null); const translate = useCallback((path: string, fallback: string) => ( hasMessage(path) ? t(path) : fallback ), [hasMessage, t]); const activeMeta = useMemo( () => withTranslatedMeta(getChannelMeta(selectedChannelType), translate), [selectedChannelType, translate], ); const supportedFields = activeMeta.configFields.filter((field) => field.key !== 'accountId'); const canValidate = Boolean(onValidate) && activeMeta.connectionType !== 'qr'; const resolvedTitle = title ?? t('channels.modal.configureTitle', { name: activeMeta.name }); const descriptionText = description ?? activeMeta.description; const instructionsHeading = t('channels.modal.howTo'); const confirmText = confirmLabel ?? (canValidate ? t('channels.modal.saveAndConnect') : t('channels.modal.confirm')); const validateText = validateLabel ?? t('channels.modal.validateConfig'); const validatingText = validatingLabel ?? t('channels.modal.validating'); useEffect(() => { if (!open) { setShowSecrets({}); } }, [open]); useEffect(() => { setShowSecrets({}); }, [selectedChannelType]); useEffect(() => { if (open && firstFieldRef.current) { firstFieldRef.current.focus(); } }, [open, selectedChannelType]); const toggleSecretVisibility = useCallback((key: string) => { setShowSecrets((prev) => ({ ...prev, [key]: !prev[key] })); }, []); if (!open) return null; return (
{ if (event.target === event.currentTarget) { onClose(); } }} >
event.stopPropagation()} onClick={(event) => event.stopPropagation()} >

{resolvedTitle}

{descriptionText}

{error ? (
{error}
) : null}
{validationResult ? (
{validationResult.valid ? ( ) : ( )}
{validationResult.valid ? t('channels.modal.credentialsVerified') : t('channels.modal.validationFailed')}
{validationResult.errors.length > 0 ? (
    {validationResult.errors.map((item) => (
  • {item}
  • ))}
) : null} {validationResult.warnings.length > 0 ? (
{t('channels.modal.warnings')}
    {validationResult.warnings.map((item) => (
  • {item}
  • ))}
) : null}
) : null} { void onConfirm(); }} onValidate={canValidate ? () => { void onValidate?.(); } : undefined} disabled={false} submitting={submitting} validating={validating} />
); }