diff --git a/src/features/authFiles/components/AuthJsonPasteModal.tsx b/src/features/authFiles/components/AuthJsonPasteModal.tsx new file mode 100644 index 000000000..589d949ce --- /dev/null +++ b/src/features/authFiles/components/AuthJsonPasteModal.tsx @@ -0,0 +1,134 @@ +import { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Modal } from '@/components/ui/Modal'; +import { Button } from '@/components/ui/Button'; +import { Input } from '@/components/ui/Input'; +import { Select } from '@/components/ui/Select'; +import type { AuthJsonInputType } from '@/features/authFiles/sessionAuthConverter'; +import styles from '@/pages/AuthFilesPage.module.scss'; + +type AuthJsonPasteModalProps = { + open: boolean; + saving: boolean; + onClose: () => void; + onSave: (type: AuthJsonInputType, fileName: string, jsonText: string) => Promise; +}; + +const DEFAULT_FILE_NAME = 'codex-account.json'; +const INVALID_BASE_FILE_NAME_PATTERN = /[\\/:*?"<>|]/; + +const isValidBaseJsonFileName = (value: string) => + value.toLowerCase().endsWith('.json') && + !INVALID_BASE_FILE_NAME_PATTERN.test(value) && + !Array.from(value).some((char) => char.charCodeAt(0) < 32); + +export function AuthJsonPasteModal({ open, saving, onClose, onSave }: AuthJsonPasteModalProps) { + const { t } = useTranslation(); + const [type, setType] = useState('session'); + const [fileName, setFileName] = useState(DEFAULT_FILE_NAME); + const [jsonText, setJsonText] = useState(''); + const [error, setError] = useState(''); + + const resetForm = () => { + setType('session'); + setFileName(DEFAULT_FILE_NAME); + setJsonText(''); + setError(''); + }; + + const handleClose = () => { + resetForm(); + onClose(); + }; + + const options = useMemo( + () => [ + { value: 'cpa', label: t('auth_files.paste_type_cpa') }, + { value: 'session', label: t('auth_files.paste_type_session') }, + ], + [t] + ); + + const handleSave = async () => { + const trimmedName = fileName.trim(); + if (!trimmedName) { + setError(t('auth_files.paste_error_file_name')); + return; + } + if (!isValidBaseJsonFileName(trimmedName)) { + setError(t('auth_files.paste_error_file_name_invalid')); + return; + } + if (!jsonText.trim()) { + setError(t('auth_files.paste_error_json_required')); + return; + } + + setError(''); + try { + await onSave(type, trimmedName, jsonText); + resetForm(); + } catch (err) { + setError(err instanceof Error ? err.message : t('notification.save_failed')); + } + }; + + return ( + + + + + } + > +
+ {error &&
{error}
} +
+ + setFileName(event.target.value)} + disabled={saving} + placeholder={DEFAULT_FILE_NAME} + /> +
+ +