diff --git a/frontend/package.json b/frontend/package.json index e3923d7..25ba0fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,12 +15,12 @@ "@marsidev/react-turnstile": "^0.3.1", "@mui/icons-material": "^5.14.9", "@mui/material": "^5.14.10", + "ahooks": "^3.7.8", "immer": "^10.0.2", "jotai": "^2.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.16.0", - "swr": "^2.2.2" + "react-router-dom": "^6.16.0" }, "devDependencies": { "@types/node": "^20.6.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 192d465..89e5e7f 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: '@mui/material': specifier: ^5.14.10 version: 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) + ahooks: + specifier: ^3.7.8 + version: 3.7.8(react@18.2.0) immer: specifier: ^10.0.2 version: 10.0.2 @@ -35,9 +38,6 @@ dependencies: react-router-dom: specifier: ^6.16.0 version: 6.16.0(react-dom@18.2.0)(react@18.2.0) - swr: - specifier: ^2.2.2 - version: 2.2.2(react@18.2.0) devDependencies: '@types/node': @@ -873,6 +873,10 @@ packages: resolution: {integrity: sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==} dev: true + /@types/js-cookie@2.2.7: + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + dev: false + /@types/json-schema@7.0.13: resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} dev: true @@ -1070,6 +1074,29 @@ packages: hasBin: true dev: true + /ahooks-v3-count@1.0.0: + resolution: {integrity: sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==} + dev: false + + /ahooks@3.7.8(react@18.2.0): + resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==} + engines: {node: '>=8.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.22.15 + '@types/js-cookie': 2.2.7 + ahooks-v3-count: 1.0.0 + dayjs: 1.11.10 + intersection-observer: 0.12.2 + js-cookie: 2.2.1 + lodash: 4.17.21 + react: 18.2.0 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.6.2 + dev: false + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -1155,10 +1182,6 @@ packages: supports-color: 7.2.0 dev: true - /client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false - /clsx@2.0.0: resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} engines: {node: '>=6'} @@ -1216,6 +1239,10 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /dayjs@1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -1603,6 +1630,10 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + dev: false + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: false @@ -1655,6 +1686,10 @@ packages: react: 18.2.0 dev: false + /js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: false @@ -1711,6 +1746,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1940,6 +1979,10 @@ packages: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} dev: false + /resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1985,6 +2028,11 @@ packages: loose-envify: 1.4.0 dev: false + /screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + dev: false + /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} @@ -2055,16 +2103,6 @@ packages: engines: {node: '>= 0.4'} dev: false - /swr@2.2.2(react@18.2.0): - resolution: {integrity: sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - dependencies: - client-only: 0.0.1 - react: 18.2.0 - use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -2090,6 +2128,10 @@ packages: typescript: 5.2.2 dev: true + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2114,14 +2156,6 @@ packages: punycode: 2.3.0 dev: true - /use-sync-external-store@1.2.0(react@18.2.0): - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /vite@4.4.9(@types/node@20.6.2): resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} engines: {node: ^14.18.0 || >=16.0.0} diff --git a/frontend/src/components/CaptchaWidget.tsx b/frontend/src/components/CaptchaWidget.tsx index 98841f4..204f0d7 100644 --- a/frontend/src/components/CaptchaWidget.tsx +++ b/frontend/src/components/CaptchaWidget.tsx @@ -1,26 +1,40 @@ import { Turnstile } from '@marsidev/react-turnstile' import Button from '@mui/material/Button' -import { useRef, useState, memo } from 'react' +import { useRef, useState, memo, forwardRef, useImperativeHandle } from 'react' import type { TurnstileInstance } from '@marsidev/react-turnstile' -import useSWR from "swr"; import { ApiCaptcha } from '@/apis/model'; import Alert from '@mui/material/Alert'; import Skeleton from '@mui/material/Skeleton'; +import { useRequest } from 'ahooks'; interface prop { onSuccess: ((token: string) => void) } -function CaptchaWidget({ onSuccess }: prop) { - const ref = useRef(null) +export type refType = { + reload: () => void +} + + +const CaptchaWidget = forwardRef(({ onSuccess }, ref) => { + const Turnstileref = useRef(null) const [key, setKey] = useState(1) - const { data, error, isLoading } = useSWR(import.meta.env.VITE_APIADDR + '/api/v1/captcha') + const { data, error, loading } = useRequest(() => fetch(import.meta.env.VITE_APIADDR + '/api/v1/captcha').then(v => v.json() as Promise)) + + useImperativeHandle(ref, () => { + return { + reload: () => { + setKey(key + 1) + } + } + }) + if (error) { console.warn(error) return {String(error)} } - if (isLoading) { + if (loading) { return } if (data?.code != 0) { @@ -31,13 +45,14 @@ function CaptchaWidget({ onSuccess }: prop) { onSuccess("ok") return <> } + return ( <> - + ) -} +}) const CaptchaWidgetMemo = memo(CaptchaWidget) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 8c1d783..c308be8 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,16 +2,11 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import CssBaseline from '@mui/material/CssBaseline'; -import { SWRConfig } from 'swr' ReactDOM.createRoot(document.getElementById('root')!).render( - fetch(resource, init).then(res => res.json()) - }}> - ) diff --git a/frontend/src/views/Register.tsx b/frontend/src/views/Register.tsx index 084dba9..1da5327 100644 --- a/frontend/src/views/Register.tsx +++ b/frontend/src/views/Register.tsx @@ -11,26 +11,41 @@ import Container from '@mui/material/Container'; import { Link as RouterLink } from "react-router-dom"; import { register } from '@/apis/apis' import CheckInput, { refType } from '@/components/CheckInput' -import { useState } from 'react'; +import { useRef, useState } from 'react'; import Alert from '@mui/material/Alert'; import Snackbar from '@mui/material/Snackbar'; import Loading from '@/components/Loading' import { useNavigate } from "react-router-dom"; import CaptchaWidget from '@/components/CaptchaWidget'; +import { useRequest } from 'ahooks'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' export default function SignUp() { const [regErr, setRegErr] = useState(""); - const [loading, setLoading] = useState(false); const navigate = useNavigate(); const [captchaToken, setCaptchaToken] = useState(""); + const captchaRef = useRef(null) const checkList = React.useRef>(new Map()) + const { loading, run } = useRequest(register, { + manual: true, + onSuccess: () => { + navigate("/login") + }, + onError: (e) => { + setRegErr(String(e)) + console.warn(e) + captchaRef.current?.reload() + } + }) + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - if (loading) return - setLoading(true) + if (loading) { + return + } const data = new FormData(event.currentTarget); const d = { email: data.get('email')?.toString(), @@ -38,17 +53,14 @@ export default function SignUp() { username: data.get("username")?.toString() } if (!Array.from(checkList.current.values()).map(v => v.verify()).reduce((p, v) => (p == true) && (v == true))) { - setLoading(false) return } if (captchaToken == "") { - setLoading(false) setRegErr("验证码无效") } - register(d.email ?? "", d.username ?? "", d.password ?? "", captchaToken). - then(() => navigate("/login")). - catch(v => [setRegErr(String(v)), console.warn(v)]). - finally(() => setLoading(false)) + + run(d.email ?? "", d.username ?? "", d.password ?? "", captchaToken) + }; return ( @@ -126,7 +138,7 @@ export default function SignUp() { /> - +