diff --git a/apps/admin/package.json b/apps/admin/package.json index e49cbdd..1f59920 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@faker-js/faker": "^10.0.0", - "@lottiefiles/dotlottie-react": "^0.17.7", + "@lottiefiles/dotlottie-react": "^0.17.15", "@noble/curves": "^2.0.1", "@stripe/react-stripe-js": "^5.4.0", "@stripe/stripe-js": "^8.5.2", diff --git a/apps/admin/public/assets/locales/en-US/auth.json b/apps/admin/public/assets/locales/en-US/auth.json index e2decec..06eebf3 100644 --- a/apps/admin/public/assets/locales/en-US/auth.json +++ b/apps/admin/public/assets/locales/en-US/auth.json @@ -1,4 +1,11 @@ { + "captcha": { + "clickToRefresh": "Click to refresh", + "noImage": "No Image", + "placeholder": "Enter captcha code...", + "refresh": "Refresh captcha", + "required": "Please enter captcha code" + }, "check": { "description": "Verify your identity", "title": "Verify" diff --git a/apps/admin/public/assets/locales/en-US/system.json b/apps/admin/public/assets/locales/en-US/system.json index 7c67319..e362625 100644 --- a/apps/admin/public/assets/locales/en-US/system.json +++ b/apps/admin/public/assets/locales/en-US/system.json @@ -104,13 +104,20 @@ }, "userSecuritySettings": "User & Security", "verify": { - "description": "Configure Turnstile CAPTCHA and verification settings", - "enableLoginVerify": "Enable Verification on Login", - "enableLoginVerifyDescription": "When enabled, users must pass human verification during login", - "enablePasswordVerify": "Enable Verification on Password Reset", - "enablePasswordVerifyDescription": "When enabled, users must pass human verification during password reset", - "enableRegisterVerify": "Enable Verification on Registration", - "enableRegisterVerifyDescription": "When enabled, users must pass human verification during registration", + "captchaType": "Captcha Type", + "captchaTypeDescription": "Choose between local image captcha (offline) or Cloudflare Turnstile", + "captchaTypeLocal": "Local Image Captcha", + "captchaTypePlaceholder": "Select captcha type", + "captchaTypeTurnstile": "Cloudflare Turnstile", + "description": "Configure captcha type and verification settings", + "enableAdminLoginCaptcha": "Enable Admin Authentication Captcha", + "enableAdminLoginCaptchaDescription": "When enabled, administrators must pass captcha verification during login or password reset", + "enableUserLoginCaptcha": "Enable User Login Captcha", + "enableUserLoginCaptchaDescription": "When enabled, users must pass captcha verification during login", + "enableUserRegisterCaptcha": "Enable User Registration Captcha", + "enableUserRegisterCaptchaDescription": "When enabled, users must pass captcha verification during registration", + "enableUserResetPasswordCaptcha": "Enable User Password Reset Captcha", + "enableUserResetPasswordCaptchaDescription": "When enabled, users must pass captcha verification during password reset", "saveFailed": "Save Failed", "saveSuccess": "Save Successful", "title": "Security Verification", diff --git a/apps/admin/public/assets/locales/en-US/user.json b/apps/admin/public/assets/locales/en-US/user.json index 7dfb941..bb92767 100644 --- a/apps/admin/public/assets/locales/en-US/user.json +++ b/apps/admin/public/assets/locales/en-US/user.json @@ -147,5 +147,6 @@ "address": "Address", "noNodesAvailable": "No nodes available", "nodeGroup": "Node Group", - "publicNodes": "Public Nodes" + "publicNodes": "Public Nodes", + "subscriptionNodes": "Subscription Nodes" } diff --git a/apps/admin/public/assets/locales/zh-CN/auth.json b/apps/admin/public/assets/locales/zh-CN/auth.json index 2a4c8fa..86abb3c 100644 --- a/apps/admin/public/assets/locales/zh-CN/auth.json +++ b/apps/admin/public/assets/locales/zh-CN/auth.json @@ -1,4 +1,11 @@ { + "captcha": { + "clickToRefresh": "点击刷新", + "noImage": "无图片", + "placeholder": "请输入验证码...", + "refresh": "刷新验证码", + "required": "请输入验证码" + }, "check": { "description": "验证您的身份", "title": "验证" diff --git a/apps/admin/public/assets/locales/zh-CN/system.json b/apps/admin/public/assets/locales/zh-CN/system.json index 3b3c01e..49d4134 100644 --- a/apps/admin/public/assets/locales/zh-CN/system.json +++ b/apps/admin/public/assets/locales/zh-CN/system.json @@ -104,13 +104,20 @@ }, "userSecuritySettings": "用户与安全", "verify": { - "description": "配置 Turnstile 验证码和验证设置", - "enableLoginVerify": "登录验证", - "enableLoginVerifyDescription": "启用后,用户登录时必须通过人机验证", - "enablePasswordVerify": "密码重置验证", - "enablePasswordVerifyDescription": "启用后,用户重置密码时必须通过人机验证", - "enableRegisterVerify": "注册验证", - "enableRegisterVerifyDescription": "启用后,用户注册时必须通过人机验证", + "captchaType": "验证码类型", + "captchaTypeDescription": "选择本地图形验证码(离线)或 Cloudflare Turnstile", + "captchaTypeLocal": "本地图形验证码", + "captchaTypePlaceholder": "选择验证码类型", + "captchaTypeTurnstile": "Cloudflare Turnstile", + "description": "配置验证码类型和验证设置", + "enableAdminLoginCaptcha": "启用管理端认证验证码", + "enableAdminLoginCaptchaDescription": "启用后,管理员登录或重置密码时必须通过验证码验证", + "enableUserLoginCaptcha": "启用用户端登录验证码", + "enableUserLoginCaptchaDescription": "启用后,用户登录时必须通过验证码验证", + "enableUserRegisterCaptcha": "启用用户端注册验证码", + "enableUserRegisterCaptchaDescription": "启用后,用户注册时必须通过验证码验证", + "enableUserResetPasswordCaptcha": "启用用户重置密码验证码", + "enableUserResetPasswordCaptchaDescription": "启用后,用户重置密码时必须通过验证码验证", "saveFailed": "保存失败", "saveSuccess": "保存成功", "title": "安全验证", diff --git a/apps/admin/public/assets/locales/zh-CN/user.json b/apps/admin/public/assets/locales/zh-CN/user.json index 1c2a0d0..5a93923 100644 --- a/apps/admin/public/assets/locales/zh-CN/user.json +++ b/apps/admin/public/assets/locales/zh-CN/user.json @@ -147,5 +147,6 @@ "address": "地址", "noNodesAvailable": "无可用节点", "nodeGroup": "节点组", - "publicNodes": "公共节点" + "publicNodes": "公共节点", + "subscriptionNodes": "套餐节点" } diff --git a/apps/admin/src/sections/auth/email/login-form.tsx b/apps/admin/src/sections/auth/email/login-form.tsx index 9f9014a..f402ecf 100644 --- a/apps/admin/src/sections/auth/email/login-form.tsx +++ b/apps/admin/src/sections/auth/email/login-form.tsx @@ -10,12 +10,13 @@ import { import { Input } from "@workspace/ui/components/input"; import { Icon } from "@workspace/ui/composed/icon"; import type { Dispatch, SetStateAction } from "react"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { z } from "zod"; import { useGlobalStore } from "@/stores/global"; import CloudFlareTurnstile, { type TurnstileRef } from "../turnstile"; +import LocalCaptcha, { type LocalCaptchaRef } from "../local-captcha"; export default function LoginForm({ loading, @@ -33,14 +34,25 @@ export default function LoginForm({ const { t } = useTranslation("auth"); const { common } = useGlobalStore(); const { verify } = common; + const [captchaId, setCaptchaId] = useState(""); + + const isTurnstile = verify.captcha_type === "turnstile"; + const isLocal = verify.captcha_type === "local"; + const captchaEnabled = verify.enable_admin_login_captcha; const formSchema = z.object({ - email: z.email(t("login.email", "Email")), + email: z + .string() + .email(t("login.email", "Please enter a valid email address")), password: z.string(), cf_token: - verify.enable_login_verify && verify.turnstile_site_key + captchaEnabled && isTurnstile && verify.turnstile_site_key ? z.string() : z.string().optional(), + captcha_code: + captchaEnabled && isLocal + ? z.string().min(1, t("captcha.required", "Please enter captcha code")) + : z.string().optional(), }); const form = useForm>({ resolver: zodResolver(formSchema), @@ -48,11 +60,17 @@ export default function LoginForm({ }); const turnstile = useRef(null); + const localCaptcha = useRef(null); const handleSubmit = form.handleSubmit((data) => { try { + // Add captcha_id for local captcha + if (isLocal && captchaEnabled) { + (data as any).captcha_id = captchaId; + } onSubmit(data); } catch (_error) { turnstile.current?.reset(); + localCaptcha.current?.reset(); } }); @@ -98,7 +116,7 @@ export default function LoginForm({ )} /> - {verify.enable_login_verify && ( + {captchaEnabled && isTurnstile && ( )} + {captchaEnabled && isLocal && ( + ( + + + + + + + )} + /> + )} + + ); + } +); + +LocalCaptcha.displayName = "LocalCaptcha"; + +export default LocalCaptcha; diff --git a/apps/admin/src/sections/system/user-security/verify-form.tsx b/apps/admin/src/sections/system/user-security/verify-form.tsx index 18c71d7..d4593a9 100644 --- a/apps/admin/src/sections/system/user-security/verify-form.tsx +++ b/apps/admin/src/sections/system/user-security/verify-form.tsx @@ -11,6 +11,13 @@ import { FormMessage, } from "@workspace/ui/components/form"; import { ScrollArea } from "@workspace/ui/components/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@workspace/ui/components/select"; import { Sheet, SheetContent, @@ -33,11 +40,13 @@ import { toast } from "sonner"; import { z } from "zod"; const verifySchema = z.object({ + captcha_type: z.string().optional(), turnstile_site_key: z.string().optional(), turnstile_secret: z.string().optional(), - enable_register_verify: z.boolean().optional(), - enable_login_verify: z.boolean().optional(), - enable_reset_password_verify: z.boolean().optional(), + enable_user_login_captcha: z.boolean().optional(), + enable_user_register_captcha: z.boolean().optional(), + enable_admin_login_captcha: z.boolean().optional(), + enable_user_reset_password_captcha: z.boolean().optional(), }); type VerifyFormData = z.infer; @@ -59,11 +68,13 @@ export default function VerifyConfig() { const form = useForm({ resolver: zodResolver(verifySchema), defaultValues: { + captcha_type: "local", turnstile_site_key: "", turnstile_secret: "", - enable_register_verify: false, - enable_login_verify: false, - enable_reset_password_verify: false, + enable_user_login_captcha: false, + enable_user_register_captcha: false, + enable_admin_login_captcha: false, + enable_user_reset_password_captcha: false, }, }); @@ -126,26 +137,42 @@ export default function VerifyConfig() { > ( - {t("verify.turnstileSiteKey", "Turnstile Site Key")} + {t("verify.captchaType", "Captcha Type")} - + > + + + + + + {t("verify.captchaTypeLocal", "Local Image Captcha")} + + + {t( + "verify.captchaTypeTurnstile", + "Cloudflare Turnstile" + )} + + + {t( - "verify.turnstileSiteKeyDescription", - "Cloudflare Turnstile site key for frontend verification" + "verify.captchaTypeDescription", + "Choose between local image captcha (offline) or Cloudflare Turnstile" )} @@ -153,45 +180,78 @@ export default function VerifyConfig() { )} /> - ( - - - {t("verify.turnstileSecret", "Turnstile Secret Key")} - - - - - - {t( - "verify.turnstileSecretDescription", - "Cloudflare Turnstile secret key for backend verification" - )} - - - - )} - /> + {form.watch("captcha_type") === "turnstile" && ( + <> + ( + + + {t("verify.turnstileSiteKey", "Turnstile Site Key")} + + + + + + {t( + "verify.turnstileSiteKeyDescription", + "Cloudflare Turnstile site key for frontend verification" + )} + + + + )} + /> + + ( + + + {t("verify.turnstileSecret", "Turnstile Secret Key")} + + + + + + {t( + "verify.turnstileSecretDescription", + "Cloudflare Turnstile secret key for backend verification" + )} + + + + )} + /> + + )} ( {t( - "verify.enableRegisterVerify", - "Enable Verification on Registration" + "verify.enableUserLoginCaptcha", + "Enable User Login Captcha" )} @@ -203,8 +263,8 @@ export default function VerifyConfig() { {t( - "verify.enableRegisterVerifyDescription", - "When enabled, users must pass human verification during registration" + "verify.enableUserLoginCaptchaDescription", + "When enabled, users must pass captcha verification during login" )} @@ -214,13 +274,13 @@ export default function VerifyConfig() { ( {t( - "verify.enableLoginVerify", - "Enable Verification on Login" + "verify.enableUserRegisterCaptcha", + "Enable User Registration Captcha" )} @@ -232,8 +292,8 @@ export default function VerifyConfig() { {t( - "verify.enableLoginVerifyDescription", - "When enabled, users must pass human verification during login" + "verify.enableUserRegisterCaptchaDescription", + "When enabled, users must pass captcha verification during registration" )} @@ -243,13 +303,13 @@ export default function VerifyConfig() { ( {t( - "verify.enablePasswordVerify", - "Enable Verification on Password Reset" + "verify.enableUserResetPasswordCaptcha", + "Enable User Password Reset Captcha" )} @@ -261,8 +321,37 @@ export default function VerifyConfig() { {t( - "verify.enablePasswordVerifyDescription", - "When enabled, users must pass human verification during password reset" + "verify.enableUserResetPasswordCaptchaDescription", + "When enabled, users must pass captcha verification during password reset" + )} + + + + )} + /> + + ( + + + {t( + "verify.enableAdminLoginCaptcha", + "Enable Admin Authentication Captcha" + )} + + + + + + {t( + "verify.enableAdminLoginCaptchaDescription", + "When enabled, administrators must pass captcha verification during login" )} diff --git a/apps/admin/src/sections/user/index.tsx b/apps/admin/src/sections/user/index.tsx index a8343f6..67b613b 100644 --- a/apps/admin/src/sections/user/index.tsx +++ b/apps/admin/src/sections/user/index.tsx @@ -477,7 +477,12 @@ function PreviewNodesDialog({ userId }: { userId: number }) { {previewData.node_groups.map((group) => (

- {group.name || (group.id === 0 ? t("publicNodes", "Public Nodes") : `${t("nodeGroup", "Node Group")} ${group.id}`)} + {group.name || + (group.id === -1 + ? t("subscriptionNodes", "Subscription Nodes") + : group.id === 0 + ? t("publicNodes", "Public Nodes") + : `${t("nodeGroup", "Node Group")} ${group.id}`)}

{group.nodes && group.nodes.length > 0 ? ( diff --git a/apps/admin/src/stores/global.tsx b/apps/admin/src/stores/global.tsx index 9660293..8bdcfef 100644 --- a/apps/admin/src/stores/global.tsx +++ b/apps/admin/src/stores/global.tsx @@ -49,9 +49,14 @@ export const useGlobalStore = create((set, get) => ({ }, verify: { turnstile_site_key: "", + captcha_type: "turnstile", enable_login_verify: false, enable_register_verify: false, enable_reset_password_verify: false, + enable_user_login_captcha: false, + enable_user_register_captcha: false, + enable_user_reset_password_captcha: false, + enable_admin_login_captcha: false, }, auth: { mobile: { diff --git a/apps/user/package.json b/apps/user/package.json index c6ce1dc..22af014 100644 --- a/apps/user/package.json +++ b/apps/user/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@faker-js/faker": "^10.0.0", - "@lottiefiles/dotlottie-react": "^0.17.7", + "@lottiefiles/dotlottie-react": "^0.17.15", "@stripe/react-stripe-js": "^5.4.0", "@stripe/stripe-js": "^8.5.2", "@tailwindcss/vite": "^4.0.6", diff --git a/apps/user/public/assets/locales.zip b/apps/user/public/assets/locales.zip new file mode 100644 index 0000000..0fadcad Binary files /dev/null and b/apps/user/public/assets/locales.zip differ diff --git a/apps/user/public/assets/locales/en-US/auth.json b/apps/user/public/assets/locales/en-US/auth.json index 4e1baeb..f45fc03 100644 --- a/apps/user/public/assets/locales/en-US/auth.json +++ b/apps/user/public/assets/locales/en-US/auth.json @@ -1,12 +1,21 @@ { "authenticating": "Authenticating...", "binding": "Binding account...", + "captcha": { + "clickToRefresh": "Click to refresh", + "noImage": "No Image", + "placeholder": "Enter captcha code...", + "refresh": "Refresh captcha", + "required": "Please enter captcha code" + }, "get": "Get Code", "login": { "codeLogin": "Login with Code", "email": "Please enter a valid email address", + "emailPlaceholder": "Enter your email...", "forgotPassword": "Forgot Password?", "passwordLogin": "Login with Password", + "passwordPlaceholder": "Enter your password...", "registerAccount": "Register Account", "success": "Login successful!", "title": "Login" @@ -17,19 +26,28 @@ }, "privacyPolicy": "Privacy Policy", "register": { + "areaCodePlaceholder": "Area code...", + "codePlaceholder": "Enter code...", "email": "Please enter a valid email address", + "emailPlaceholder": "Enter your email...", "existingAccount": "Already have an account?", "invite": "Invitation Code (Optional)", "message": "Registration is currently disabled", "passwordMismatch": "Passwords do not match", + "passwordPlaceholder": "Enter your password...", + "repeatPasswordPlaceholder": "Enter password again...", "success": "Registration successful!", "switchToLogin": "Login", + "telephonePlaceholder": "Enter your telephone...", "title": "Register", "whitelist": "This email domain is not in the whitelist" }, "reset": { + "codePlaceholder": "Enter code...", "email": "Please enter a valid email address", + "emailPlaceholder": "Enter your email...", "existingAccount": "Remember your password?", + "passwordPlaceholder": "Enter your new password...", "success": "Password reset successful!", "switchToLogin": "Login", "title": "Reset Password" diff --git a/apps/user/public/assets/locales/zh-CN/auth.json b/apps/user/public/assets/locales/zh-CN/auth.json index 6fd7c9c..e408d59 100644 --- a/apps/user/public/assets/locales/zh-CN/auth.json +++ b/apps/user/public/assets/locales/zh-CN/auth.json @@ -1,12 +1,21 @@ { "authenticating": "正在认证...", "binding": "正在绑定账号...", + "captcha": { + "clickToRefresh": "点击刷新", + "noImage": "无图片", + "placeholder": "请输入验证码...", + "refresh": "刷新验证码", + "required": "请输入验证码" + }, "get": "获取验证码", "login": { "codeLogin": "验证码登录", "email": "请输入有效的邮箱地址", + "emailPlaceholder": "请输入邮箱...", "forgotPassword": "忘记密码?", "passwordLogin": "密码登录", + "passwordPlaceholder": "请输入密码...", "registerAccount": "注册账号", "success": "登录成功!", "title": "登录" @@ -17,19 +26,28 @@ }, "privacyPolicy": "隐私政策", "register": { + "areaCodePlaceholder": "区号...", + "codePlaceholder": "请输入验证码...", "email": "请输入有效的邮箱地址", + "emailPlaceholder": "请输入邮箱...", "existingAccount": "已有账号?", "invite": "邀请码(可选)", "message": "注册功能暂时不可用", "passwordMismatch": "两次密码输入不一致", + "passwordPlaceholder": "请输入密码...", + "repeatPasswordPlaceholder": "请再次输入密码...", "success": "注册成功!", "switchToLogin": "登录", + "telephonePlaceholder": "请输入手机号...", "title": "注册", "whitelist": "该邮箱域名不在白名单中" }, "reset": { + "codePlaceholder": "请输入验证码...", "email": "请输入有效的邮箱地址", + "emailPlaceholder": "请输入邮箱...", "existingAccount": "记得密码了?", + "passwordPlaceholder": "请输入新密码...", "success": "密码重置成功!", "switchToLogin": "登录", "title": "重置密码" diff --git a/apps/user/src/sections/auth/email/login-form.tsx b/apps/user/src/sections/auth/email/login-form.tsx index 69e8045..8dff1b2 100644 --- a/apps/user/src/sections/auth/email/login-form.tsx +++ b/apps/user/src/sections/auth/email/login-form.tsx @@ -10,13 +10,14 @@ import { import { Input } from "@workspace/ui/components/input"; import { Icon } from "@workspace/ui/composed/icon"; import type { Dispatch, SetStateAction } from "react"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { z } from "zod"; import { useGlobalStore } from "@/stores/global"; import type { TurnstileRef } from "../turnstile"; import CloudFlareTurnstile from "../turnstile"; +import LocalCaptcha, { type LocalCaptchaRef } from "../local-captcha"; export default function LoginForm({ loading, @@ -34,14 +35,23 @@ export default function LoginForm({ const { t } = useTranslation("auth"); const { common } = useGlobalStore(); const { verify } = common; + const [captchaId, setCaptchaId] = useState(""); + + const isTurnstile = verify.captcha_type === "turnstile"; + const isLocal = verify.captcha_type === "local"; + const captchaEnabled = verify.enable_user_login_captcha; const formSchema = z.object({ email: z.email(t("login.email", "Please enter a valid email address")), password: z.string(), cf_token: - verify.enable_login_verify && verify.turnstile_site_key + captchaEnabled && isTurnstile && verify.turnstile_site_key ? z.string() : z.string().optional(), + captcha_code: + captchaEnabled && isLocal + ? z.string().min(1, t("captcha.required", "Please enter captcha code")) + : z.string().optional(), }); const form = useForm>({ resolver: zodResolver(formSchema), @@ -49,11 +59,17 @@ export default function LoginForm({ }); const turnstile = useRef(null); + const localCaptcha = useRef(null); const handleSubmit = form.handleSubmit((data) => { try { + // Add captcha_id for local captcha + if (isLocal && captchaEnabled) { + (data as any).captcha_id = captchaId; + } onSubmit(data); } catch (_error) { turnstile.current?.reset(); + localCaptcha.current?.reset(); } }); @@ -68,7 +84,7 @@ export default function LoginForm({ @@ -84,7 +100,7 @@ export default function LoginForm({ @@ -93,7 +109,7 @@ export default function LoginForm({ )} /> - {verify.enable_login_verify && ( + {captchaEnabled && isTurnstile && ( )} + {captchaEnabled && isLocal && ( + ( + + + + + + + )} + /> + )} + + ); + } +); + +LocalCaptcha.displayName = "LocalCaptcha"; + +export default LocalCaptcha; diff --git a/apps/user/src/sections/auth/phone/login-form.tsx b/apps/user/src/sections/auth/phone/login-form.tsx index e60e0a7..622a779 100644 --- a/apps/user/src/sections/auth/phone/login-form.tsx +++ b/apps/user/src/sections/auth/phone/login-form.tsx @@ -19,6 +19,7 @@ import { useGlobalStore } from "@/stores/global"; import SendCode from "../send-code"; import type { TurnstileRef } from "../turnstile"; import CloudFlareTurnstile from "../turnstile"; +import LocalCaptcha, { type LocalCaptchaRef } from "../local-captcha"; export default function LoginForm({ loading, @@ -34,6 +35,11 @@ export default function LoginForm({ const { t } = useTranslation("auth"); const { common } = useGlobalStore(); const { verify } = common; + const [captchaId, setCaptchaId] = useState(""); + + const isTurnstile = verify.captcha_type === "turnstile"; + const isLocal = verify.captcha_type === "local"; + const captchaEnabled = verify.enable_user_login_captcha; const formSchema = z.object({ telephone_area_code: z.string(), @@ -41,9 +47,13 @@ export default function LoginForm({ telephone_code: z.string().optional(), password: z.string().optional(), cf_token: - verify.enable_login_verify && verify.turnstile_site_key + captchaEnabled && isTurnstile && verify.turnstile_site_key ? z.string() : z.string().optional(), + captcha_code: + captchaEnabled && isLocal + ? z.string().min(1, t("captcha.required", "Please enter captcha code")) + : z.string().optional(), }); const form = useForm>({ resolver: zodResolver(formSchema), @@ -53,11 +63,17 @@ export default function LoginForm({ const [mode, setMode] = useState<"password" | "code">("password"); const turnstile = useRef(null); + const localCaptcha = useRef(null); const handleSubmit = form.handleSubmit((data) => { try { + // Add captcha_id for local captcha + if (isLocal && captchaEnabled) { + (data as any).captcha_id = captchaId; + } onSubmit(data); } catch (_error) { turnstile.current?.reset(); + localCaptcha.current?.reset(); } }); @@ -88,7 +104,7 @@ export default function LoginForm({ ); } }} - placeholder="Area code..." + placeholder={t("register.areaCodePlaceholder", "Area code...")} simple value={field.value} /> @@ -99,7 +115,7 @@ export default function LoginForm({ /> @@ -119,7 +135,9 @@ export default function LoginForm({
)} /> - {verify.enable_login_verify && ( + {captchaEnabled && isTurnstile && ( )} + {captchaEnabled && isLocal && ( + ( + + + + + + + )} + /> + )}