import React, { useMemo, useState } from 'react'; import { Linking, Pressable, Text, TextInput, View } from 'react-native'; import { CreditCard } from 'lucide-react-native'; import { conversationStyles } from '../../features/conversation/conversationStyles'; import { themedColor } from '../../theme'; import type { SuperagentToolRendererProps } from '../../types'; import { configurePspCredentialsTitle, getPspProcessorName, getPspSchema, isSecretPspField, normalizePspFields, pspCredentialsErrorMessage, sanitizeHttpUrl, showsPspForm, type PspField, } from '../interactiveWidgetUtils'; import { ApprovalCard } from '../primitives/ApprovalCard'; import { toolWidgetStyles } from '../primitives/toolWidgetStyles'; import { getWidgetStatus, parseToolArgs } from '../toolWidgetUtils'; import { useApprovalSubmit } from '../useApprovalSubmit'; import { FallbackActionCard, SelectableOptionRow, StatusRowWithError } from './interactiveWidgetParts'; /** * configure_psp_credentials — native port of the web ConfigurePspCredentials * form. Connect submits the field values as the user_input (approve), Skip * submits {skipped: true} (also approve — the backend resolves skip as * success). Secret fields (Wix type PASSWORD) are masked and autofill is * suppressed; credential values are never echoed anywhere else. Required-only * validation, matching the web (validation_pattern is model-controlled and * running it client-side is a ReDoS vector — the backend re-validates). The * form renders for waiting AND error (the backend keeps this tool submittable * on error for in-place retry — web showForm parity, with the failure reason * shown above the fields), gated on isLastAssistantMessage so a stale card * can't resume an old turn out of order. */ export function ConfigurePspCredentialsWidget({ isLastAssistantMessage, submitToolCallInput, toolCall }: SuperagentToolRendererProps) { const args = useMemo(() => parseToolArgs(toolCall), [toolCall]); const fields = useMemo(() => normalizePspFields(args), [args]); const status = getWidgetStatus(toolCall.status); const { canAct, error, isSubmitting, submit } = useApprovalSubmit(toolCall, submitToolCallInput); const [values, setValues] = useState>({}); const processorName = getPspProcessorName(args); const resultError = status === 'error' ? pspCredentialsErrorMessage(toolCall.results, processorName) : null; if (showsPspForm(status, isLastAssistantMessage)) { if (fields.length === 0) { return ( submit(true, { skipped: true }) : undefined} /> ); } const schema = getPspSchema(args); const allFilled = fields.every((field) => (values[field.name] ?? '').trim().length > 0); return ( submit(true, values) : undefined} onReject={canAct ? () => submit(true, { skipped: true }) : undefined} rejectLabel="Skip for now" title={`Connect ${processorName}`} > Enter your {processorName} credentials to activate payments. {resultError ? {resultError} : null} {fields.map((field) => ( setValues((current) => ({ ...current, [field.name]: value }))} value={values[field.name] ?? ''} /> ))} ); } return ( ); } function PspFieldInput({ field, isSubmitting, onChange, value, }: { field: PspField; isSubmitting: boolean; onChange: (value: string) => void; value: string; }) { return ( {field.label} * {field.options.length > 0 ? ( {field.options.map((option) => ( onChange(option.value)} selected={value === option.value} /> ))} ) : ( )} ); } function PspLink({ label, url }: { label: string; url: string | null }) { if (!url) return null; return ( { void Linking.openURL(url); }}> {label} ); }