refactor: improve form validation in CodexConfigEditor using HTML5 validation API

- Replace custom error state with native HTML5 form validation
- Add useRef hooks for input field validation management
- Add pattern attributes to enforce non-empty input validation
- Leverage browser's built-in validation UI for better UX
- Extract closeTemplateModal function for consistent modal closing
This commit is contained in:
farion1231
2025-09-21 20:04:50 +08:00
parent c4c1747563
commit d041ea7a56

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import { X, Save } from "lucide-react"; import { X, Save } from "lucide-react";
@@ -92,8 +92,11 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
const [templateWebsiteUrl, setTemplateWebsiteUrl] = useState(""); const [templateWebsiteUrl, setTemplateWebsiteUrl] = useState("");
const [templateModelName, setTemplateModelName] = useState("gpt-5-codex"); const [templateModelName, setTemplateModelName] = useState("gpt-5-codex");
const apiKeyInputRef = useRef<HTMLInputElement>(null);
const [templateError, setTemplateError] = useState(""); const baseUrlInputRef = useRef<HTMLInputElement>(null);
const modelNameInputRef = useRef<HTMLInputElement>(null);
// 移除自动填充逻辑,因为现在在点击自定义按钮时就已经填充 // 移除自动填充逻辑,因为现在在点击自定义按钮时就已经填充
@@ -125,19 +128,31 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
setIsCommonConfigModalOpen(false); setIsCommonConfigModalOpen(false);
}; };
const closeTemplateModal = () => {
setIsTemplateModalOpen(false);
};
const applyTemplate = () => { const applyTemplate = () => {
const requiredInputs = [
apiKeyInputRef.current,
baseUrlInputRef.current,
modelNameInputRef.current,
];
for (const input of requiredInputs) {
if (input && !input.checkValidity()) {
input.reportValidity();
input.focus();
return;
}
}
const trimmedKey = templateApiKey.trim(); const trimmedKey = templateApiKey.trim();
const trimmedBaseUrl = templateBaseUrl.trim(); const trimmedBaseUrl = templateBaseUrl.trim();
const trimmedModel = templateModelName.trim(); const trimmedModel = templateModelName.trim();
if (!trimmedKey || !trimmedBaseUrl || !trimmedModel) {
setTemplateError("请填写 API 密钥、API 基础地址和模型名称");
return;
}
const auth = generateThirdPartyAuth(trimmedKey); const auth = generateThirdPartyAuth(trimmedKey);
const config = generateThirdPartyConfig( const config = generateThirdPartyConfig(
@@ -170,9 +185,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
setTemplateModelName("gpt-5-codex"); setTemplateModelName("gpt-5-codex");
setTemplateError(""); closeTemplateModal();
setIsTemplateModalOpen(false);
}; };
const handleTemplateInputKeyDown = ( const handleTemplateInputKeyDown = (
@@ -310,7 +323,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
className="fixed inset-0 z-50 flex items-center justify-center" className="fixed inset-0 z-50 flex items-center justify-center"
onMouseDown={(e) => { onMouseDown={(e) => {
if (e.target === e.currentTarget) { if (e.target === e.currentTarget) {
setIsTemplateModalOpen(false); closeTemplateModal();
} }
}} }}
> >
@@ -329,7 +342,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
<button <button
type="button" type="button"
onClick={() => setIsTemplateModalOpen(false)} onClick={closeTemplateModal}
className="rounded-md p-1 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100" className="rounded-md p-1 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100"
aria-label="关闭" aria-label="关闭"
> >
@@ -354,12 +367,11 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
<input <input
type="text" type="text"
value={templateApiKey} value={templateApiKey}
onChange={(e) => { ref={apiKeyInputRef}
setTemplateApiKey(e.target.value); onChange={(e) => setTemplateApiKey(e.target.value)}
setTemplateError("");
}}
onKeyDown={handleTemplateInputKeyDown} onKeyDown={handleTemplateInputKeyDown}
pattern=".*\S.*"
title="请输入有效的内容"
placeholder="sk-your-api-key-here" placeholder="sk-your-api-key-here"
required required
className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm font-mono text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100" className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm font-mono text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
@@ -393,11 +405,8 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
<input <input
type="url" type="url"
value={templateBaseUrl} value={templateBaseUrl}
onChange={(e) => { ref={baseUrlInputRef}
setTemplateBaseUrl(e.target.value); onChange={(e) => setTemplateBaseUrl(e.target.value)}
setTemplateError("");
}}
onKeyDown={handleTemplateInputKeyDown} onKeyDown={handleTemplateInputKeyDown}
placeholder="https://your-api-endpoint.com/v1" placeholder="https://your-api-endpoint.com/v1"
required required
@@ -432,12 +441,11 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
<input <input
type="text" type="text"
value={templateModelName} value={templateModelName}
onChange={(e) => { ref={modelNameInputRef}
setTemplateModelName(e.target.value); onChange={(e) => setTemplateModelName(e.target.value)}
setTemplateError("");
}}
onKeyDown={handleTemplateInputKeyDown} onKeyDown={handleTemplateInputKeyDown}
pattern=".*\S.*"
title="请输入有效的内容"
placeholder="gpt-5-codex" placeholder="gpt-5-codex"
required required
className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100" className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
@@ -489,17 +497,12 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
</div> </div>
)} )}
{templateError && (
<p className="text-sm text-red-500 dark:text-red-400">
{templateError}
</p>
)}
</div> </div>
<div className="flex items-center justify-end gap-3 border-t border-gray-200 bg-gray-100 p-6 dark:border-gray-800 dark:bg-gray-800"> <div className="flex items-center justify-end gap-3 border-t border-gray-200 bg-gray-100 p-6 dark:border-gray-800 dark:bg-gray-800">
<button <button
type="button" type="button"
onClick={() => setIsTemplateModalOpen(false)} onClick={closeTemplateModal}
className="rounded-lg px-4 py-2 text-sm font-medium text-gray-500 transition-colors hover:bg-white hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-100" className="rounded-lg px-4 py-2 text-sm font-medium text-gray-500 transition-colors hover:bg-white hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-100"
> >