import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Text, TextInput, View } from "react-native"; import { useTranslation } from "react-i18next"; import { StyleSheet } from "react-native-unistyles"; import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet"; import { Button } from "@/components/ui/button"; import { Field, FormTextInput } from "@/components/ui/form-field"; export interface ProfileDraft { name: string; command: string; args: string; } interface FieldErrors { name?: string; command?: string; } interface TerminalProfileEditModalProps { visible: boolean; title: string; initialDraft: ProfileDraft; onClose: () => void; onSave: (draft: ProfileDraft) => Promise; testID?: string; } export function TerminalProfileEditModal({ visible, title, initialDraft, onClose, onSave, testID, }: TerminalProfileEditModalProps) { const { t } = useTranslation(); const [name, setName] = useState(initialDraft.name); const [command, setCommand] = useState(initialDraft.command); const [args, setArgs] = useState(initialDraft.args); const [fieldErrors, setFieldErrors] = useState({}); const [submitError, setSubmitError] = useState(null); const [isPending, setIsPending] = useState(false); const nameInputRef = useRef(null); const commandInputRef = useRef(null); const argsInputRef = useRef(null); const handleNameChange = useCallback((value: string) => { setName(value); setFieldErrors((current) => ({ ...current, name: undefined })); }, []); const handleCommandChange = useCallback((value: string) => { setCommand(value); setFieldErrors((current) => ({ ...current, command: undefined })); }, []); const handleArgsChange = useCallback((value: string) => { setArgs(value); }, []); const sheetHeader = useMemo(() => ({ title }), [title]); useEffect(() => { if (!visible) { setIsPending(false); return; } setName(initialDraft.name); setCommand(initialDraft.command); setArgs(initialDraft.args); setFieldErrors({}); setSubmitError(null); setIsPending(false); const timeout = setTimeout(() => { nameInputRef.current?.focus(); }, 50); return () => clearTimeout(timeout); }, [visible, initialDraft.name, initialDraft.command, initialDraft.args]); const validate = useCallback((): boolean => { const errors: FieldErrors = {}; if (name.trim().length === 0) { errors.name = t("settings.host.terminalProfiles.nameRequired"); } if (command.trim().length === 0) { errors.command = t("settings.host.terminalProfiles.commandRequired"); } setFieldErrors(errors); return Object.keys(errors).length === 0; }, [command, name, t]); const handleSave = useCallback(async () => { if (isPending) return; setSubmitError(null); if (!validate()) { return; } setIsPending(true); try { await onSave({ name: name.trim(), command: command.trim(), args }); onClose(); } catch (error) { setSubmitError(error instanceof Error ? error.message : t("common.errors.unableToSave")); } finally { setIsPending(false); } }, [args, command, isPending, name, onClose, onSave, t, validate]); const handleCancel = useCallback(() => { if (isPending) return; onClose(); }, [isPending, onClose]); const handleNameSubmit = useCallback(() => { commandInputRef.current?.focus(); }, []); const handleCommandSubmit = useCallback(() => { argsInputRef.current?.focus(); }, []); const handleArgsSubmit = useCallback(() => { void handleSave(); }, [handleSave]); const nameError = fieldErrors.name; const commandError = fieldErrors.command; return ( {submitError ? ( {submitError} ) : null} ); } const styles = StyleSheet.create((theme) => ({ body: { gap: theme.spacing[4], paddingBottom: theme.spacing[2], }, actions: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], marginTop: theme.spacing[2], }, actionButton: { flex: 1, }, submitError: { color: theme.colors.palette.red[300], fontSize: theme.fontSize.base, }, }));