From 8bf6ce2c2599b092b6fab93508c8fa4fa8a0cec5 Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 26 Nov 2025 12:26:02 +0800 Subject: [PATCH] feat(provider): add required field validation with toast notifications - Add validation for provider name (required for all providers) - Add validation for API endpoint and API Key (required for non-official providers) - Use toast notifications instead of inline form errors for better visibility - Move name validation from zod schema to handleSubmit for consistent UX - Add i18n keys: endpointRequired, apiKeyRequired --- .../providers/forms/ProviderForm.tsx | 67 +++++++++++++++++++ src/i18n/locales/en.json | 2 + src/i18n/locales/zh.json | 2 + src/lib/schemas/provider.ts | 2 +- 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index a38ed4519..2a5a63228 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState, useCallback } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider"; @@ -399,6 +400,72 @@ export function ProviderForm({ } } + // 供应商名称必填校验 + if (!values.name.trim()) { + toast.error( + t("providerForm.fillSupplierName", { + defaultValue: "请填写供应商名称", + }), + ); + return; + } + + // 非官方供应商必填校验:端点和 API Key + if (category !== "official") { + if (appId === "claude") { + if (!baseUrl.trim()) { + toast.error( + t("providerForm.endpointRequired", { + defaultValue: "非官方供应商请填写 API 端点", + }), + ); + return; + } + if (!apiKey.trim()) { + toast.error( + t("providerForm.apiKeyRequired", { + defaultValue: "非官方供应商请填写 API Key", + }), + ); + return; + } + } else if (appId === "codex") { + if (!codexBaseUrl.trim()) { + toast.error( + t("providerForm.endpointRequired", { + defaultValue: "非官方供应商请填写 API 端点", + }), + ); + return; + } + if (!codexApiKey.trim()) { + toast.error( + t("providerForm.apiKeyRequired", { + defaultValue: "非官方供应商请填写 API Key", + }), + ); + return; + } + } else if (appId === "gemini") { + if (!geminiBaseUrl.trim()) { + toast.error( + t("providerForm.endpointRequired", { + defaultValue: "非官方供应商请填写 API 端点", + }), + ); + return; + } + if (!geminiApiKey.trim()) { + toast.error( + t("providerForm.apiKeyRequired", { + defaultValue: "非官方供应商请填写 API Key", + }), + ); + return; + } + } + } + let settingsConfig: string; // Codex: 组合 auth 和 config diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 57f251608..8e94c6660 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -275,6 +275,8 @@ "fillConfigContent": "Please fill in configuration content", "fillParameter": "Please fill in {{label}}", "fillTemplateValue": "Please fill in {{label}}", + "endpointRequired": "API endpoint is required for non-official providers", + "apiKeyRequired": "API Key is required for non-official providers", "configJsonError": "Config JSON format error, please check syntax", "authJsonRequired": "auth.json must be a JSON object", "authJsonError": "auth.json format error, please check JSON syntax", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index d11e8eb52..a362e33dc 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -275,6 +275,8 @@ "fillConfigContent": "请填写配置内容", "fillParameter": "请填写 {{label}}", "fillTemplateValue": "请填写 {{label}}", + "endpointRequired": "非官方供应商请填写 API 端点", + "apiKeyRequired": "非官方供应商请填写 API Key", "configJsonError": "配置JSON格式错误,请检查语法", "authJsonRequired": "auth.json 必须是 JSON 对象", "authJsonError": "auth.json 格式错误,请检查JSON语法", diff --git a/src/lib/schemas/provider.ts b/src/lib/schemas/provider.ts index cfaee713c..e26509b6d 100644 --- a/src/lib/schemas/provider.ts +++ b/src/lib/schemas/provider.ts @@ -36,7 +36,7 @@ function parseJsonError(error: unknown): string { } export const providerSchema = z.object({ - name: z.string().min(1, "请填写供应商名称"), + name: z.string(), // 必填校验移至 handleSubmit 中用 toast 提示 websiteUrl: z.string().url("请输入有效的网址").optional().or(z.literal("")), notes: z.string().optional(), settingsConfig: z