From ae338f88ee4c5022d330fd8a6bfd518a5335d4ee Mon Sep 17 00:00:00 2001 From: alikia2x Date: Sat, 31 May 2025 11:47:45 +0800 Subject: [PATCH] update: the error dialog --- packages/next/app/signup/SignUpForm.tsx | 86 ++++++++++++++++--------- packages/next/bun.lock | 11 ++++ packages/next/components/ui/Dialog.tsx | 57 ++++++++++++---- packages/next/package.json | 3 +- 4 files changed, 111 insertions(+), 46 deletions(-) diff --git a/packages/next/app/signup/SignUpForm.tsx b/packages/next/app/signup/SignUpForm.tsx index f1e6f20..5480742 100644 --- a/packages/next/app/signup/SignUpForm.tsx +++ b/packages/next/app/signup/SignUpForm.tsx @@ -9,6 +9,13 @@ import { ApiRequestError } from "@/lib/net"; import { Portal } from "@/components/utils/Portal"; import { Dialog, DialogButton, DialogButtonGroup, DialogHeadline, DialogSupportingText } from "@/components/ui/Dialog"; import { FilledButton } from "@/components/ui/Buttons/FilledButton"; +import { string, object, ValidationError } from "yup"; + +const FormSchema = object({ + username: string().required().max(50), + password: string().required().min(4).max(120), + nickname: string().optional().max(30) +}); interface CaptchaSessionResponse { g: string; @@ -37,9 +44,9 @@ interface RegistrationFormProps { } const SignUpForm: React.FC = ({ backendURL }) => { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [nickname, setNickname] = useState(""); + const [usernameInput, setUsername] = useState(""); + const [passwordInput, setPassword] = useState(""); + const [nicknameInput, setNickname] = useState(""); const [loading, setLoading] = useState(false); const [showDialog, setShowDialog] = useState(false); const [dialogContent, setDialogContent] = useState(<>); @@ -68,6 +75,44 @@ const SignUpForm: React.FC = ({ backendURL }) => { }; const register = async () => { + let username: string | undefined; + let password: string | undefined; + let nickname: string | undefined; + try { + const formData = await FormSchema.validate({ + username: usernameInput, + password: passwordInput, + nickname: nicknameInput + }); + username = formData.username; + password = formData.password; + nickname = formData.nickname; + } catch (e) { + if (!(e instanceof ValidationError)) { + return; + } + setShowDialog(true); + setDialogContent( + <> + 错误 + +

注册信息填写有误,请检查后重新提交。

+ 错误信息: +
+
    + {e.errors.map((item, i) => { + return
  1. {item}
  2. ; + })} +
+
+ + setShowDialog(false)}>Close + + + ); + return; + } + setLoading(true); try { if (!captchaSession?.g || !captchaSession?.n || !captchaSession?.t || !captchaSession?.id) { @@ -92,9 +137,9 @@ const SignUpForm: React.FC = ({ backendURL }) => { Authorization: `Bearer ${captchaResult.token}` }, body: JSON.stringify({ - username, - password, - nickname + username: username, + password: password, + nickname: nickname }) }); @@ -124,7 +169,7 @@ const SignUpForm: React.FC = ({ backendURL }) => { > = ({ backendURL }) => { - { - setShowDialog(true); - setDialogContent( - <> - Error - -

Your operation frequency is too high. Please try again later. (RATE_LIMIT_EXCEED)

-
- - setShowDialog(false)}>Close - - - ); - }} - size="m" - shape="square" - > - Show Dialog -
- + {!loading ? 注册 : } diff --git a/packages/next/bun.lock b/packages/next/bun.lock index 86c3a87..b680736 100644 --- a/packages/next/bun.lock +++ b/packages/next/bun.lock @@ -9,6 +9,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "swr": "^2.3.3", + "yup": "^1.6.1", }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -213,6 +214,8 @@ "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], + "property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="], + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], @@ -239,8 +242,14 @@ "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + "tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="], + + "toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], @@ -249,6 +258,8 @@ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yup": ["yup@1.6.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], diff --git a/packages/next/components/ui/Dialog.tsx b/packages/next/components/ui/Dialog.tsx index f12e0f0..49ddf59 100644 --- a/packages/next/components/ui/Dialog.tsx +++ b/packages/next/components/ui/Dialog.tsx @@ -18,32 +18,60 @@ interface DialogProps { children?: React.ReactNode; } -interface OptionalChidrenProps { +type OptionalChidrenProps> = T & { children?: React.ReactNode; -} +}; -type DialogHeadlineProps = OptionalChidrenProps; -type DialogSupportingTextProps = OptionalChidrenProps; -type DialogButtonGroupProps = OptionalChidrenProps; +type HeadElementAttr = React.HTMLAttributes; +type DivElementAttr = React.HTMLAttributes; +type ButtonElementAttr = React.HTMLAttributes; -interface DialogButtonProps extends OptionalChidrenProps { +type DialogHeadlineProps = OptionalChidrenProps; +type DialogSupportingTextProps = OptionalChidrenProps; +type DialogButtonGroupProps = OptionalChidrenProps; + +interface DialogButtonProps extends OptionalChidrenProps { onClick?: React.MouseEventHandler; } -export const DialogHeadline: React.FC = ({ children }: DialogHeadlineProps) => { - return

{children}

; +export const DialogHeadline: React.FC = ({ + children, + className, + ...rest +}: DialogHeadlineProps) => { + return ( +

+ {children} +

+ ); }; -export const DialogSupportingText: React.FC = ({ children }: DialogHeadlineProps) => { - return
{children}
; +export const DialogSupportingText: React.FC = ({ + children, + className, + ...rest +}: DialogHeadlineProps) => { + return ( +
+ {children} +
+ ); }; -export const DialogButton: React.FC = ({ children, onClick }: DialogButtonProps) => { - return {children}; +export const DialogButton: React.FC = ({ children, onClick, ...rest }: DialogButtonProps) => { + return ( + + {children} + + ); }; -export const DialogButtonGroup: React.FC = ({ children }: DialogButtonGroupProps) => { - return
{children}
; +export const DialogButtonGroup: React.FC = ({ children, ...rest }: DialogButtonGroupProps) => { + return ( +
+ {children} +
+ ); }; export const Dialog: React.FC = ({ show, children }: DialogProps) => { @@ -67,6 +95,7 @@ export const Dialog: React.FC = ({ show, children }: DialogProps) = animate={{ opacity: 1, transform: "scale(1)" }} exit={{ opacity: 0 }} transition={{ ease: [0.31, 0.69, 0.3, 1.02], duration: 0.3 }} + aria-modal="true" > {children} diff --git a/packages/next/package.json b/packages/next/package.json index 4ae6bb8..69269d6 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -14,7 +14,8 @@ "next": "^15.1.8", "react": "^19.1.0", "react-dom": "^19.1.0", - "swr": "^2.3.3" + "swr": "^2.3.3", + "yup": "^1.6.1" }, "devDependencies": { "typescript": "^5",