完善验证页面和后端接口的链接
> 1. 配置了开发环境的端口转发 -> localhost:8080\
> 2. 完成了注册,登录,忘记密码页的功能
> 3. 为项目配置了vitest测试框架
> 4. 对这三个页面进行了测试
> 重写了/test/setup.ts
Change-Id: I46c600ce06d698dae6953b2e1e3ff4a98b0f3de4
diff --git a/package.json b/package.json
index 6f5a635..8c00989 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,14 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "test": "vitest"
},
"dependencies": {
"@ant-design/icons": "^6.0.0",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@reduxjs/toolkit": "^2.8.2",
+ "@types/jest": "^29.5.14",
"antd": "^5.25.2",
"axios": "^1.9.0",
"less": "^4.3.0",
@@ -23,6 +25,8 @@
},
"devDependencies": {
"@eslint/js": "^9.25.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.3.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1",
@@ -30,6 +34,7 @@
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
+ "jsdom": "^26.1.0",
"typescript": "~5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^6.3.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e3fae56..56f73c4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@
'@reduxjs/toolkit':
specifier: ^2.8.2
version: 2.8.2(react-redux@9.2.0(@types/react@19.1.4)(react@19.1.0)(redux@5.0.1))(react@19.1.0)
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
antd:
specifier: ^5.25.2
version: 5.25.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -42,6 +45,12 @@
'@eslint/js':
specifier: ^9.25.0
version: 9.27.0
+ '@testing-library/jest-dom':
+ specifier: ^6.6.3
+ version: 6.6.3
+ '@testing-library/react':
+ specifier: ^16.3.0
+ version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@types/react':
specifier: ^19.1.2
version: 19.1.4
@@ -50,7 +59,7 @@
version: 19.1.5(@types/react@19.1.4)
'@vitejs/plugin-react':
specifier: ^4.4.1
- version: 4.4.1(vite@6.3.5(less@4.3.0))
+ version: 4.4.1(vite@6.3.5(@types/node@22.15.30)(less@4.3.0))
eslint:
specifier: ^9.25.0
version: 9.27.0
@@ -63,6 +72,9 @@
globals:
specifier: ^16.0.0
version: 16.1.0
+ jsdom:
+ specifier: ^26.1.0
+ version: 26.1.0
typescript:
specifier: ~5.8.3
version: 5.8.3
@@ -71,13 +83,16 @@
version: 8.32.1(eslint@9.27.0)(typescript@5.8.3)
vite:
specifier: ^6.3.5
- version: 6.3.5(less@4.3.0)
+ version: 6.3.5(@types/node@22.15.30)(less@4.3.0)
vitest:
specifier: ^3.1.4
- version: 3.1.4(less@4.3.0)
+ version: 3.1.4(@types/node@22.15.30)(jsdom@26.1.0)(less@4.3.0)
packages:
+ '@adobe/css-tools@4.4.3':
+ resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==}
+
'@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
@@ -138,6 +153,9 @@
react: '>=19.0.0'
react-dom: '>=19.0.0'
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@@ -221,6 +239,34 @@
resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==}
engines: {node: '>=6.9.0'}
+ '@csstools/color-helpers@5.0.2':
+ resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.0.10':
+ resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
'@emotion/hash@0.8.0':
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
@@ -435,6 +481,18 @@
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@jest/expect-utils@29.7.0':
+ resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/schemas@29.6.3':
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/types@29.6.3':
+ resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
'@jridgewell/gen-mapping@0.3.8':
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'}
@@ -637,12 +695,41 @@
cpu: [x64]
os: [win32]
+ '@sinclair/typebox@0.27.8':
+ resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
+
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
+ '@testing-library/dom@10.4.0':
+ resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
+ engines: {node: '>=18'}
+
+ '@testing-library/jest-dom@6.6.3':
+ resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==}
+ engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+
+ '@testing-library/react@16.3.0':
+ resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@testing-library/dom': ^10.0.0
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -658,9 +745,24 @@
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+ '@types/istanbul-lib-coverage@2.0.6':
+ resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+
+ '@types/istanbul-lib-report@3.0.3':
+ resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
+
+ '@types/istanbul-reports@3.0.4':
+ resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
+
+ '@types/jest@29.5.14':
+ resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/node@22.15.30':
+ resolution: {integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==}
+
'@types/react-dom@19.1.5':
resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==}
peerDependencies:
@@ -669,9 +771,18 @@
'@types/react@19.1.4':
resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==}
+ '@types/stack-utils@2.0.3':
+ resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+
'@types/use-sync-external-store@0.0.6':
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
+ '@types/yargs-parser@21.0.3':
+ resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
+
+ '@types/yargs@17.0.33':
+ resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}
+
'@typescript-eslint/eslint-plugin@8.32.1':
resolution: {integrity: sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -764,13 +875,25 @@
engines: {node: '>=0.4.0'}
hasBin: true
+ agent-base@7.1.3:
+ resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
+ engines: {node: '>= 14'}
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
antd@5.25.2:
resolution: {integrity: sha512-7R2nUvlHhey7Trx64+hCtGXOiy+DTUs1Lv5bwbV1LzEIZIhWb0at1AM6V3K108a5lyoR9n7DX3ptlLF7uYV/DQ==}
peerDependencies:
@@ -780,6 +903,13 @@
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -827,6 +957,10 @@
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
engines: {node: '>=12'}
+ chalk@3.0.0:
+ resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
+ engines: {node: '>=8'}
+
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -835,6 +969,10 @@
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
+ ci-info@3.9.0:
+ resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
+ engines: {node: '>=8'}
+
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
@@ -872,9 +1010,20 @@
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ css.escape@1.5.1:
+ resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
+ cssstyle@4.3.1:
+ resolution: {integrity: sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==}
+ engines: {node: '>=18'}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@@ -887,6 +1036,9 @@
supports-color:
optional: true
+ decimal.js@10.5.0:
+ resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
+
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
@@ -898,6 +1050,20 @@
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ dom-accessibility-api@0.6.3:
+ resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -905,6 +1071,10 @@
electron-to-chromium@1.5.155:
resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
+ entities@6.0.0:
+ resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
+ engines: {node: '>=0.12'}
+
errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
hasBin: true
@@ -937,6 +1107,10 @@
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ escape-string-regexp@2.0.0:
+ resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
+ engines: {node: '>=8'}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -1001,6 +1175,10 @@
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
engines: {node: '>=12.0.0'}
+ expect@29.7.0:
+ resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -1123,6 +1301,18 @@
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
@@ -1151,6 +1341,10 @@
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -1163,12 +1357,35 @@
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
is-what@3.14.1:
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ jest-diff@29.7.0:
+ resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-get-type@29.6.3:
+ resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-matcher-utils@29.7.0:
+ resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-message-util@29.7.0:
+ resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-util@29.7.0:
+ resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -1176,6 +1393,15 @@
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
+ jsdom@26.1.0:
+ resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -1217,12 +1443,22 @@
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
loupe@3.1.3:
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -1255,6 +1491,10 @@
engines: {node: '>=4'}
hasBin: true
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1281,6 +1521,9 @@
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+ nwsapi@2.2.20:
+ resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -1301,6 +1544,9 @@
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'}
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -1339,6 +1585,14 @@
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
@@ -1585,6 +1839,9 @@
peerDependencies:
react: ^19.1.0
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
@@ -1618,6 +1875,10 @@
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
redux-thunk@3.1.0:
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies:
@@ -1645,6 +1906,9 @@
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -1654,6 +1918,10 @@
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -1687,6 +1955,10 @@
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -1695,6 +1967,10 @@
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
+ stack-utils@2.0.6:
+ resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
+ engines: {node: '>=10'}
+
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@@ -1704,6 +1980,10 @@
string-convert@0.2.1:
resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==}
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -1715,6 +1995,9 @@
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
throttle-debounce@5.0.2:
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
engines: {node: '>=12.22'}
@@ -1741,6 +2024,13 @@
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
+ tldts-core@6.1.86:
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+ tldts@6.1.86:
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -1748,6 +2038,14 @@
toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
+ tough-cookie@5.1.2:
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
+
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@@ -1773,6 +2071,9 @@
engines: {node: '>=14.17'}
hasBin: true
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
update-browserslist-db@1.1.3:
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
hasBin: true
@@ -1860,6 +2161,26 @@
jsdom:
optional: true
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -1874,6 +2195,25 @@
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ ws@8.18.2:
+ resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -1883,6 +2223,8 @@
snapshots:
+ '@adobe/css-tools@4.4.3': {}
+
'@ampproject/remapping@2.3.0':
dependencies:
'@jridgewell/gen-mapping': 0.3.8
@@ -1958,6 +2300,14 @@
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
+ '@asamuzakjp/css-color@3.2.0':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.27.1
@@ -2070,6 +2420,26 @@
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@csstools/color-helpers@5.0.2': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.0.2
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
'@emotion/hash@0.8.0': {}
'@emotion/unitless@0.7.5': {}
@@ -2206,6 +2576,23 @@
'@humanwhocodes/retry@0.4.3': {}
+ '@jest/expect-utils@29.7.0':
+ dependencies:
+ jest-get-type: 29.6.3
+
+ '@jest/schemas@29.6.3':
+ dependencies:
+ '@sinclair/typebox': 0.27.8
+
+ '@jest/types@29.6.3':
+ dependencies:
+ '@jest/schemas': 29.6.3
+ '@types/istanbul-lib-coverage': 2.0.6
+ '@types/istanbul-reports': 3.0.4
+ '@types/node': 22.15.30
+ '@types/yargs': 17.0.33
+ chalk: 4.1.2
+
'@jridgewell/gen-mapping@0.3.8':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -2382,10 +2769,45 @@
'@rollup/rollup-win32-x64-msvc@4.41.0':
optional: true
+ '@sinclair/typebox@0.27.8': {}
+
'@standard-schema/spec@1.0.0': {}
'@standard-schema/utils@0.3.0': {}
+ '@testing-library/dom@10.4.0':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/runtime': 7.27.1
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ chalk: 4.1.2
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ pretty-format: 27.5.1
+
+ '@testing-library/jest-dom@6.6.3':
+ dependencies:
+ '@adobe/css-tools': 4.4.3
+ aria-query: 5.3.2
+ chalk: 3.0.0
+ css.escape: 1.5.1
+ dom-accessibility-api: 0.6.3
+ lodash: 4.17.21
+ redent: 3.0.0
+
+ '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@babel/runtime': 7.27.1
+ '@testing-library/dom': 10.4.0
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@types/aria-query@5.0.4': {}
+
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.27.2
@@ -2409,8 +2831,27 @@
'@types/estree@1.0.7': {}
+ '@types/istanbul-lib-coverage@2.0.6': {}
+
+ '@types/istanbul-lib-report@3.0.3':
+ dependencies:
+ '@types/istanbul-lib-coverage': 2.0.6
+
+ '@types/istanbul-reports@3.0.4':
+ dependencies:
+ '@types/istanbul-lib-report': 3.0.3
+
+ '@types/jest@29.5.14':
+ dependencies:
+ expect: 29.7.0
+ pretty-format: 29.7.0
+
'@types/json-schema@7.0.15': {}
+ '@types/node@22.15.30':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/react-dom@19.1.5(@types/react@19.1.4)':
dependencies:
'@types/react': 19.1.4
@@ -2419,8 +2860,16 @@
dependencies:
csstype: 3.1.3
+ '@types/stack-utils@2.0.3': {}
+
'@types/use-sync-external-store@0.0.6': {}
+ '@types/yargs-parser@21.0.3': {}
+
+ '@types/yargs@17.0.33':
+ dependencies:
+ '@types/yargs-parser': 21.0.3
+
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint@9.27.0)(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -2498,14 +2947,14 @@
'@typescript-eslint/types': 8.32.1
eslint-visitor-keys: 4.2.0
- '@vitejs/plugin-react@4.4.1(vite@6.3.5(less@4.3.0))':
+ '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.30)(less@4.3.0))':
dependencies:
'@babel/core': 7.27.1
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
- vite: 6.3.5(less@4.3.0)
+ vite: 6.3.5(@types/node@22.15.30)(less@4.3.0)
transitivePeerDependencies:
- supports-color
@@ -2516,13 +2965,13 @@
chai: 5.2.0
tinyrainbow: 2.0.0
- '@vitest/mocker@3.1.4(vite@6.3.5(less@4.3.0))':
+ '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@22.15.30)(less@4.3.0))':
dependencies:
'@vitest/spy': 3.1.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 6.3.5(less@4.3.0)
+ vite: 6.3.5(@types/node@22.15.30)(less@4.3.0)
'@vitest/pretty-format@3.1.4':
dependencies:
@@ -2555,6 +3004,8 @@
acorn@8.14.1: {}
+ agent-base@7.1.3: {}
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -2562,10 +3013,14 @@
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ ansi-regex@5.0.1: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
+ ansi-styles@5.2.0: {}
+
antd@5.25.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@ant-design/colors': 7.2.1
@@ -2626,6 +3081,12 @@
argparse@2.0.1: {}
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
+ aria-query@5.3.2: {}
+
assertion-error@2.0.1: {}
asynckit@0.4.0: {}
@@ -2679,6 +3140,11 @@
loupe: 3.1.3
pathval: 2.0.0
+ chalk@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -2686,6 +3152,8 @@
check-error@2.1.1: {}
+ ci-info@3.9.0: {}
+
classnames@2.5.1: {}
color-convert@2.0.1:
@@ -2720,20 +3188,42 @@
shebang-command: 2.0.0
which: 2.0.2
+ css.escape@1.5.1: {}
+
+ cssstyle@4.3.1:
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
csstype@3.1.3: {}
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
dayjs@1.11.13: {}
debug@4.4.1:
dependencies:
ms: 2.1.3
+ decimal.js@10.5.0: {}
+
deep-eql@5.0.2: {}
deep-is@0.1.4: {}
delayed-stream@1.0.0: {}
+ dequal@2.0.3: {}
+
+ diff-sequences@29.6.3: {}
+
+ dom-accessibility-api@0.5.16: {}
+
+ dom-accessibility-api@0.6.3: {}
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -2742,6 +3232,8 @@
electron-to-chromium@1.5.155: {}
+ entities@6.0.0: {}
+
errno@0.1.8:
dependencies:
prr: 1.0.1
@@ -2794,6 +3286,8 @@
escalade@3.2.0: {}
+ escape-string-regexp@2.0.0: {}
+
escape-string-regexp@4.0.0: {}
eslint-plugin-react-hooks@5.2.0(eslint@9.27.0):
@@ -2877,6 +3371,14 @@
expect-type@1.2.1: {}
+ expect@29.7.0:
+ dependencies:
+ '@jest/expect-utils': 29.7.0
+ jest-get-type: 29.6.3
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@@ -2969,8 +3471,7 @@
gopd@1.2.0: {}
- graceful-fs@4.2.11:
- optional: true
+ graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
@@ -2986,10 +3487,27 @@
dependencies:
function-bind: 1.1.2
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.3
+ debug: 4.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.3
+ debug: 4.4.1
+ transitivePeerDependencies:
+ - supports-color
+
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
- optional: true
ignore@5.3.2: {}
@@ -3007,6 +3525,8 @@
imurmurhash@0.1.4: {}
+ indent-string@4.0.0: {}
+
is-extglob@2.1.1: {}
is-glob@4.0.3:
@@ -3015,16 +3535,82 @@
is-number@7.0.0: {}
+ is-potential-custom-element-name@1.0.1: {}
+
is-what@3.14.1: {}
isexe@2.0.0: {}
+ jest-diff@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ diff-sequences: 29.6.3
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-get-type@29.6.3: {}
+
+ jest-matcher-utils@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ jest-diff: 29.7.0
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-message-util@29.7.0:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@jest/types': 29.6.3
+ '@types/stack-utils': 2.0.3
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-util@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 22.15.30
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ graceful-fs: 4.2.11
+ picomatch: 2.3.1
+
js-tokens@4.0.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
+ jsdom@26.1.0:
+ dependencies:
+ cssstyle: 4.3.1
+ data-urls: 5.0.0
+ decimal.js: 10.5.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.20
+ parse5: 7.3.0
+ rrweb-cssom: 0.8.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.18.2
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
@@ -3068,12 +3654,18 @@
lodash.merge@4.6.2: {}
+ lodash@4.17.21: {}
+
loupe@3.1.3: {}
+ lru-cache@10.4.3: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
+ lz-string@1.5.0: {}
+
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
@@ -3102,6 +3694,8 @@
mime@1.6.0:
optional: true
+ min-indent@1.0.1: {}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -3124,6 +3718,8 @@
node-releases@2.0.19: {}
+ nwsapi@2.2.20: {}
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -3147,6 +3743,10 @@
parse-node-version@1.0.1: {}
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.0
+
path-exists@4.0.0: {}
path-key@3.1.1: {}
@@ -3172,6 +3772,18 @@
prelude-ls@1.2.1: {}
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
+ pretty-format@29.7.0:
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
proxy-from-env@1.1.0: {}
prr@1.0.1:
@@ -3505,6 +4117,8 @@
react: 19.1.0
scheduler: 0.26.0
+ react-is@17.0.2: {}
+
react-is@18.3.1: {}
react-redux@9.2.0(@types/react@19.1.4)(react@19.1.0)(redux@5.0.1):
@@ -3528,6 +4142,11 @@
react@19.1.0: {}
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
redux-thunk@3.1.0(redux@5.0.1):
dependencies:
redux: 5.0.1
@@ -3568,16 +4187,21 @@
'@rollup/rollup-win32-x64-msvc': 4.41.0
fsevents: 2.3.3
+ rrweb-cssom@0.8.0: {}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
- safer-buffer@2.1.2:
- optional: true
+ safer-buffer@2.1.2: {}
sax@1.4.1:
optional: true
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
scheduler@0.26.0: {}
scroll-into-view-if-needed@3.1.0:
@@ -3601,17 +4225,27 @@
siginfo@2.0.0: {}
+ slash@3.0.0: {}
+
source-map-js@1.2.1: {}
source-map@0.6.1:
optional: true
+ stack-utils@2.0.6:
+ dependencies:
+ escape-string-regexp: 2.0.0
+
stackback@0.0.2: {}
std-env@3.9.0: {}
string-convert@0.2.1: {}
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
strip-json-comments@3.1.1: {}
stylis@4.3.6: {}
@@ -3620,6 +4254,8 @@
dependencies:
has-flag: 4.0.0
+ symbol-tree@3.2.4: {}
+
throttle-debounce@5.0.2: {}
tinybench@2.9.0: {}
@@ -3637,12 +4273,26 @@
tinyspy@3.0.2: {}
+ tldts-core@6.1.86: {}
+
+ tldts@6.1.86:
+ dependencies:
+ tldts-core: 6.1.86
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
toggle-selection@1.0.6: {}
+ tough-cookie@5.1.2:
+ dependencies:
+ tldts: 6.1.86
+
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
ts-api-utils@2.1.0(typescript@5.8.3):
dependencies:
typescript: 5.8.3
@@ -3665,6 +4315,8 @@
typescript@5.8.3: {}
+ undici-types@6.21.0: {}
+
update-browserslist-db@1.1.3(browserslist@4.24.5):
dependencies:
browserslist: 4.24.5
@@ -3679,13 +4331,13 @@
dependencies:
react: 19.1.0
- vite-node@3.1.4(less@4.3.0):
+ vite-node@3.1.4(@types/node@22.15.30)(less@4.3.0):
dependencies:
cac: 6.7.14
debug: 4.4.1
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 6.3.5(less@4.3.0)
+ vite: 6.3.5(@types/node@22.15.30)(less@4.3.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -3700,7 +4352,7 @@
- tsx
- yaml
- vite@6.3.5(less@4.3.0):
+ vite@6.3.5(@types/node@22.15.30)(less@4.3.0):
dependencies:
esbuild: 0.25.4
fdir: 6.4.4(picomatch@4.0.2)
@@ -3709,13 +4361,14 @@
rollup: 4.41.0
tinyglobby: 0.2.13
optionalDependencies:
+ '@types/node': 22.15.30
fsevents: 2.3.3
less: 4.3.0
- vitest@3.1.4(less@4.3.0):
+ vitest@3.1.4(@types/node@22.15.30)(jsdom@26.1.0)(less@4.3.0):
dependencies:
'@vitest/expect': 3.1.4
- '@vitest/mocker': 3.1.4(vite@6.3.5(less@4.3.0))
+ '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@22.15.30)(less@4.3.0))
'@vitest/pretty-format': 3.1.4
'@vitest/runner': 3.1.4
'@vitest/snapshot': 3.1.4
@@ -3732,9 +4385,12 @@
tinyglobby: 0.2.13
tinypool: 1.0.2
tinyrainbow: 2.0.0
- vite: 6.3.5(less@4.3.0)
- vite-node: 3.1.4(less@4.3.0)
+ vite: 6.3.5(@types/node@22.15.30)(less@4.3.0)
+ vite-node: 3.1.4(@types/node@22.15.30)(less@4.3.0)
why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 22.15.30
+ jsdom: 26.1.0
transitivePeerDependencies:
- jiti
- less
@@ -3749,6 +4405,23 @@
- tsx
- yaml
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
+ webidl-conversions@7.0.0: {}
+
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@4.0.0: {}
+
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -3760,6 +4433,12 @@
word-wrap@1.2.5: {}
+ ws@8.18.2: {}
+
+ xml-name-validator@5.0.0: {}
+
+ xmlchars@2.2.0: {}
+
yallist@3.1.1: {}
yocto-queue@0.1.0: {}
diff --git a/src/api/authApi.ts b/src/api/authApi.ts
index 3a8eb17..4a08c2d 100644
--- a/src/api/authApi.ts
+++ b/src/api/authApi.ts
@@ -1,78 +1,42 @@
-import axios from 'axios';
+import axios, { type AxiosResponse } from 'axios';
+import type { RejisterRequest , CommonResponse, ResetPasswordRequest} from './type';
+import type{ LoginRequest } from './type';
+class authAPI {
- class authAPI {
-
- // static getUserById(userId) {
- // // 例如 GET http://localhost:8080/123
- // return axios.get(`/${userId}`);
- // }
-
-
- // static updateUser(userId, data) {
- // // 例如 PUT http://localhost:8080/123 Body: { username: 'xxx', ... }
- // return axios.put(`/${userId}`, data);
- // }
- //
- //
- // static deleteUser(userId:string) {
- // // 例如 DELETE http://localhost:8080/123
- // return axios.delete(`/${userId}`);
- // }
-
-
- static sendVerificationCode(email: string) {
- // Body: { email: 'xxx@yyy.com'}
+ static sendVerificationCode(email: string): Promise<AxiosResponse<CommonResponse>> {
return axios.post('/api/sendVerification', { email });
}
+ static register(request: RejisterRequest): Promise<AxiosResponse<CommonResponse>> {
+ return axios.post('/api/register', request);
+ }
- static sendResetCode(email: string) {
- // Body: { email: 'xxx@yyy.com' }
+ static sendResetCode(email: string):Promise<AxiosResponse<CommonResponse>> {
return axios.post('/api/sendResetCode', { email });
}
- //
- // static resetPassword({ email, code, newPassword }) {
- // // Body: { email, code, newPassword }
- // return axios.post('/resetPassword', { email, code, newPassword });
- // }
- //
- //
- // static register({ username, email, verificationCode, password }) {
- // // Body: { username, email, verificationCode, password, identificationNumber? }
- // const body = {
- // username,
- // email,
- // verificationCode,
- // password,
- // };
- // return axios.post('/register', body);
- // }
- //
- // /**
- // * 刷新 JWT Token(POST /refreshToken)
- // * @param {string} oldToken 旧的 JWT(放在 header: token)
- // * @returns {Promise<AxiosResponse>}
- // */
- // static refreshToken(oldToken : string) {
- // // 因为后端是从 header 中读取旧 token,这里直接把 token 放进 headers
- // return axios.post(
- // '/refreshToken',
- // {}, // 请求体空
- // {
- // headers: {
- // token: oldToken,
- // },
- // }
- // );
- // }
- //
- //
- // static login({ email, password } : {email: string, password:string}) {
- // // Body: { email, password }
- // return axios.post('/login', { email, password });
- // }
+ static resetPassword( request: ResetPasswordRequest ):Promise<AxiosResponse<CommonResponse>> {
+ return axios.post('/api/resetPassword', request);
+ }
+
+
+ static refreshToken(oldToken : string): Promise<AxiosResponse<CommonResponse<string>>> {
+ return axios.post(
+ '/api/refreshToken',
+ {}, // 请求体空
+ {
+ headers: {
+ token: oldToken,
+ },
+ }
+ );
+ }
+
+
+ static login(loginRequest: LoginRequest): Promise<AxiosResponse<CommonResponse<string>>> {
+ return axios.post('/api/login', loginRequest);
+ }
}
diff --git a/src/api/interceptors.ts b/src/api/interceptors.ts
index f56a8d4..3945bc3 100644
--- a/src/api/interceptors.ts
+++ b/src/api/interceptors.ts
@@ -1,4 +1,4 @@
-import axios from "axios";
+import axios, { type AxiosResponse } from "axios";
// 为所有auth外请求添加token头
axios.interceptors.request.use((config) => {
@@ -14,4 +14,33 @@
return error;
} );
+
+// 统一响应拦截器
+axios.interceptors.response.use(
+ (response: AxiosResponse) => {
+ const { code, msg, data } = response.data;
+
+ return {
+ ...response, // 保留原本的响应信息
+ data: {
+ code,
+ message: msg,
+ data,
+ success: code === 0, // 根据 code 判断请求是否成功
+ },
+ };
+ },
+ (error) => {
+ return {
+ ...error.response, // 保留原本的错误响应信息
+ data: {
+ code: -1,
+ message: error.message || '请求失败',
+ data: null,
+ success: false,
+ },
+ };
+ }
+);
+
export default axios
\ No newline at end of file
diff --git a/src/api/type.ts b/src/api/type.ts
index a5dd1ca..c1acc22 100644
--- a/src/api/type.ts
+++ b/src/api/type.ts
@@ -2,9 +2,22 @@
email: string;
password: string;
}
-
-export interface LoginResponse {
- user: string;
- token: string;
- refreshToken: string;
-}
\ No newline at end of file
+
+export interface RejisterRequest {
+ username: string,
+ email: string,
+ verificationCode: string,
+ password: string,
+}
+
+export interface ResetPasswordRequest {
+ email: string,
+ code: string,
+ newPassword: string,
+}
+
+export interface CommonResponse<T= null> {
+ code: number;
+ message: string;
+ data: T;
+ }
diff --git a/src/feature/auth/AuthLayout.tsx b/src/feature/auth/AuthLayout.tsx
index cceaf2d..656d869 100644
--- a/src/feature/auth/AuthLayout.tsx
+++ b/src/feature/auth/AuthLayout.tsx
@@ -17,12 +17,9 @@
position: 'relative',
}}
>
- {/* <h1>
- 登录创驿
- </h1>
- <p>
- 与众多用户和创作者一起交流
- </p> */}
+ <Card style={{ padding: 0, margin: 0, background: 'rgba(255,255,255,0)', border: 'none' }}>
+ <img src={slogan} width="100%" />
+ </Card>
</div>
<Flex
style={{ width: 400, height: '80vh' }}
@@ -33,9 +30,7 @@
<Card>
<Outlet></Outlet>
</Card>
- <Card style={{ padding: 0, margin: 0 }}>
- <img src={slogan} width="100%" />
- </Card>
+
</Flex>
</Flex>
);
diff --git a/src/feature/auth/Forget.tsx b/src/feature/auth/Forget.tsx
index 25bf6dc..a98ffcf 100644
--- a/src/feature/auth/Forget.tsx
+++ b/src/feature/auth/Forget.tsx
@@ -1,16 +1,74 @@
import { MailOutlined, LockOutlined } from '@ant-design/icons';
-import { Button, Form, Input, message, Row, Col } from 'antd';
-import { NavLink } from 'react-router';
+import { Button, Form, Input, message, Row, Col, Modal } from 'antd';
+import { NavLink, useNavigate } from 'react-router';
import { useState, useEffect } from 'react';
+import { useForm } from 'antd/es/form/Form';
+import authApi from '../../api/authApi';
+
+// 定义表单值的类型
+interface FormValues {
+ email: string;
+ code: string;
+ password: string;
+ confirmPassword: string;
+}
+
function Forget() {
const [countdown, setCountdown] = useState(0);
- const [emailSent,] = useState(false);
+ const [emailSent, setEmailSent] = useState(false); // 是否已发送验证码
+ const [form] = useForm<FormValues>();
+ const emailValue = Form.useWatch('email', form);
+ const [messageApi, contextHolder] = message.useMessage();
+ const nav = useNavigate(); // 页面跳转
- const onFinish = async () => {
+ // 校验邮箱格式
+ function isValidEmail(email: string): boolean {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ console.log(emailRegex.test(email))
+ console.log(email)
+ return emailRegex.test(email);
+ }
+ // 发送验证码
+ const sendResetCode = async () => {
+ if (!isValidEmail(emailValue)) {
+ form.validateFields(['email']);
+ return;
+ }
+
+ await authApi.sendResetCode(emailValue).then(() => {
+ setEmailSent(true);
+ setCountdown(60);
+ }).catch((error) => {
+ messageApi.error(error?.message || '验证码发送失败');
+ });
};
+ const [modal, modalContext] = Modal.useModal();
+
+ const countDownNav = (onOk: () => void) => {
+ let secondsToGo = 5;
+ const instance = modal.success({
+ title: '重置成功',
+ content: `系统将在 ${secondsToGo} 后跳转到登陆页面.`,
+ okText: "立即跳转",
+ onOk
+ });
+
+ const timer = setInterval(() => {
+ secondsToGo -= 1;
+ instance.update({
+ content: `将在 ${secondsToGo} 后回到登陆页面.`,
+ });
+ }, 1000);
+
+ setTimeout(() => {
+ clearInterval(timer);
+ instance.destroy();
+ }, secondsToGo * 1000);
+ };
+ // 倒计时发送
useEffect(() => {
let countdownTimer = null;
@@ -27,92 +85,117 @@
};
}, [countdown]);
+ // 重新发送验证码
const resendCode = () => {
if (countdown > 0) return;
setCountdown(60);
- message.info('验证码已重新发送');
+ sendResetCode(); // 重新发送验证码
+ };
+
+ // 表单提交处理
+ const onFinish = (values: FormValues) => {
+ if (!emailSent) {
+ sendResetCode();
+ } else {
+ console.log(values);
+ authApi.resetPassword({
+ email: values.email,
+ code: values.code,
+ newPassword: values.password,
+ }).then((response) => {
+ if (response.data.code == 0) {
+ countDownNav(() => nav('/login'))
+ } else {
+ messageApi.error(response.data.message);
+ }
+ })
+ }
};
return (
- <Form
- name="forget"
- initialValues={{ remember: true }}
- style={{ maxWidth: 360 }}
- onFinish={onFinish}
- >
- <h2>重置密码</h2>
- <p>请输入您注册时使用的邮箱地址</p>
-
- <Form.Item
- name="email"
- rules={[
- { required: true, message: '请输入您的邮箱!' },
- { type: 'email', message: '请输入正确的邮箱格式' }
- ]}
+ <>
+ <Form
+ form={form}
+ name="forget"
+ initialValues={{ remember: false }}
+ onFinish={onFinish}
>
- <Input prefix={<MailOutlined />} placeholder="注册邮箱" />
- </Form.Item>
+ {contextHolder}
+ <h2>重置密码</h2>
+ <p>请输入您注册时使用的邮箱地址</p>
- {emailSent && (
- <>
- <Form.Item
- name="code"
- rules={[{ required: true, message: '请输入验证码!' }]}
- >
- <Row gutter={8}>
- <Col span={16}>
- <Input placeholder="验证码" />
- </Col>
- <Col span={8}>
- <Button
- disabled={countdown > 0}
- onClick={resendCode}
- style={{ width: '100%' }}
- >
- {countdown > 0 ? `${countdown}s后重试` : '重新发送'}
- </Button>
- </Col>
- </Row>
- </Form.Item>
+ <Form.Item
+ name="email"
+ rules={[
+ { required: true, message: '请输入您的邮箱!' },
+ { type: 'email', message: '请输入正确的邮箱格式' }
+ ]}
+ >
+ <Input prefix={<MailOutlined />} placeholder="注册邮箱" />
+ </Form.Item>
- <Form.Item
- name="password"
- rules={[
- { required: true, message: '请设置新密码!' },
- { min: 6, message: '密码长度至少为6位' }
- ]}
- >
- <Input.Password prefix={<LockOutlined />} placeholder="新密码" />
- </Form.Item>
+ {emailSent && (
+ <>
+ <Form.Item
+ name="code"
+ rules={[{ required: true, message: '请输入验证码!' }]}
+ >
+ <Row gutter={8}>
+ <Col span={16}>
+ <Input placeholder="验证码" />
+ </Col>
+ <Col span={8}>
+ <Button
+ disabled={countdown > 0}
+ onClick={resendCode}
+ style={{ width: '100%' }}
+ >
+ {countdown > 0 ? `${countdown}s后重试` : '重新发送'}
+ </Button>
+ </Col>
+ </Row>
+ </Form.Item>
- <Form.Item
- name="confirmPassword"
- dependencies={['password']}
- rules={[
- { required: true, message: '请确认新密码!' },
- ({ getFieldValue }) => ({
- validator(_, value) {
- if (!value || getFieldValue('password') === value) {
- return Promise.resolve();
- }
- return Promise.reject(new Error('两次输入的密码不一致!'));
- },
- }),
- ]}
- >
- <Input.Password prefix={<LockOutlined />} placeholder="确认新密码" />
- </Form.Item>
- </>
- )}
+ <Form.Item
+ name="password"
+ rules={[
+ { required: true, message: '请设置新密码!' },
+ { min: 6, message: '密码长度至少为6位' }
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="新密码" />
+ </Form.Item>
- <Form.Item>
- <Button block type="primary" htmlType="submit">
- {emailSent ? '确认重置' : '获取验证码'}
- </Button>
- 或 <NavLink to='/login'>返回登录</NavLink>
- </Form.Item>
- </Form>
+ <Form.Item
+ name="confirmPassword"
+ dependencies={['password']}
+ rules={[
+ { required: true, message: '请确认新密码!' },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (!value || getFieldValue('password') === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次输入的密码不一致!'));
+ },
+ }),
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="确认新密码" />
+ </Form.Item>
+ </>
+ )}
+
+ <Form.Item>
+ <Button block type="primary" htmlType="submit">
+ {emailSent ? '确认重置' : '获取验证码'}
+ </Button>
+ 或 <NavLink to='/login'>返回登录</NavLink>
+ </Form.Item>
+ </Form>
+ {modalContext}
+ </>
);
}
-export default Forget;
\ No newline at end of file
+export default Forget;
diff --git a/src/feature/auth/Login.tsx b/src/feature/auth/Login.tsx
index 14bf6c8..8043007 100644
--- a/src/feature/auth/Login.tsx
+++ b/src/feature/auth/Login.tsx
@@ -1,19 +1,48 @@
+import { Loading3QuartersOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
+import { Button, Checkbox, Flex, Form, Input } from 'antd';
+import { NavLink, useNavigate } from 'react-router';
+import { useAppDispatch, useAppSelector } from '../../store/hooks';
+import { loginUser } from './authSlice';
+import { useEffect, useRef } from 'react';
+import useMessage from 'antd/es/message/useMessage';
-import { LockOutlined, MailOutlined } from '@ant-design/icons';
-import { Button, Checkbox, Form, Input, Flex } from 'antd';
-import { NavLink } from 'react-router';
+// 定义 Form 表单的字段类型
+interface FormValues {
+ email: string;
+ password: string;
+ remember: boolean;
+}
+
function Login() {
- const onFinish = (values: unknown) => {
- console.log('Received values of form: ', values);
+ const dispatch = useAppDispatch();
+ const auth = useAppSelector(state => (state.auth));
+ const [messageApi, Message] = useMessage()
+ const nav = useRef(useNavigate())
+
+ useEffect(() => {
+ if (auth.isAuth) {
+ nav.current('/');
+ }
+ if (!auth.loading && auth.error) {
+ messageApi.error(auth.error);
+ }
+ }, [auth, messageApi, nav])
+ // 给 onFinish 参数添加类型
+ const onFinish = async (values: FormValues) => {
+ try {
+ await dispatch(loginUser({ email: values.email, password: values.password }));
+ } catch (error) {
+ console.error('登录失败', error);
+ }
};
return (
<Form
name="login"
initialValues={{ remember: true }}
- style={{ maxWidth: 360 }}
onFinish={onFinish}
>
+ {Message}
<h2>登录</h2>
<Form.Item
name="email"
@@ -32,19 +61,23 @@
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>自动登录</Checkbox>
</Form.Item>
- <NavLink to='/forget'> 忘记密码 </NavLink>
-
+ <NavLink to="/forget"> 忘记密码 </NavLink>
</Flex>
</Form.Item>
<Form.Item>
<Button block type="primary" htmlType="submit">
- 登录
+ {auth.loading ? (
+ <><Loading3QuartersOutlined /></>
+ ) : (
+ <>登录</>
+ )
+ }
</Button>
- 或 <NavLink to='/register'>注册</NavLink>
+ 或 <NavLink to="/register">注册</NavLink>
</Form.Item>
</Form>
);
-};
+}
-export default Login;
\ No newline at end of file
+export default Login;
diff --git a/src/feature/auth/Register.tsx b/src/feature/auth/Register.tsx
index 08edc70..0023b71 100644
--- a/src/feature/auth/Register.tsx
+++ b/src/feature/auth/Register.tsx
@@ -1,9 +1,12 @@
import { useEffect, useState } from 'react';
import { LockOutlined, MailOutlined, NumberOutlined, UserOutlined } from '@ant-design/icons';
-import {Button, Checkbox, Form, Input, Space} from 'antd';
-import { NavLink } from 'react-router';
-import authApi from "../../api/authApi.ts";
+import { Button, Checkbox, Form, Input, message, Space } from 'antd';
+import { NavLink, useNavigate } from 'react-router';
+import authApi from "../../api/authApi";
+import type { RejisterRequest } from "../../api/type";
+import type { AxiosResponse } from 'axios';
+// 定义表单字段的类型
interface FormValues {
name: string;
email: string;
@@ -16,42 +19,65 @@
function Register() {
const [countdown, setCountdown] = useState(0);
const [form] = Form.useForm<FormValues>();
- const emailValue = Form.useWatch('email', form)
+ const emailValue = Form.useWatch('email', form);
+ const [messageApi, contextHolder] = message.useMessage();
+ const nav = useNavigate(); // 用于页面跳转
- //
+ // 校验邮箱格式
function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-
return emailRegex.test(email);
}
+ // 发送验证码
const sendVerificationCode = () => {
- // 如果邮箱校验不通过,则触发表单校验提示,并中断
if (!isValidEmail(emailValue)) {
form.validateFields(['email']);
return;
}
- // 发起 POST 请求到后端 /sendVerification
- authApi.sendVerificationCode(emailValue).catch()
- setCountdown(60);
+ authApi.sendVerificationCode(emailValue).then(() => {
+ setCountdown(60); // 开始倒计时
+ }).catch((error) => {
+ messageApi.error(error?.message || '验证码发送失败');
+ });
};
- // 发送表单倒计时
+ // 倒计时处理
useEffect(() => {
- if (countdown > 0) {
- const timer = setTimeout(() => {
- setCountdown(prev => prev - 1);
- }, 1000);
+ if (countdown === 0) return;
+ const timer = setInterval(() => {
+ setCountdown(prev => {
+ if (prev === 1) clearInterval(timer); // 倒计时结束
+ return prev - 1;
+ });
+ }, 1000);
- return () => clearTimeout(timer);
- }
+ return () => clearInterval(timer);
}, [countdown]);
-
// 表单提交
const onFinish = (values: FormValues) => {
- console.log('注册成功:', values);
+ const registerRequest: RejisterRequest = {
+ username: values.name,
+ email: values.email,
+ verificationCode: values.verifyCode,
+ password: values.password,
+ };
+
+ authApi.register(registerRequest).then((response: AxiosResponse) => {
+ if (response.data.code === 200) {
+ messageApi.success("注册成功");
+ form.resetFields(); // 清空表单
+ setTimeout(() => {
+ nav('/login'); // 注册成功后跳转到登录页面
+ }, 1500);
+ } else {
+ messageApi.error(response.data.message);
+ }
+ }).catch((error) => {
+ messageApi.error(error?.message || '注册失败,请重试');
+ });
};
return (
@@ -62,6 +88,7 @@
scrollToFirstError
>
<h2>注册</h2>
+ {contextHolder}
<Form.Item
name="name"
@@ -72,7 +99,6 @@
<Form.Item
name="email"
-
rules={[{ required: true, message: '请输入邮箱' }, { type: 'email', message: '邮箱格式错误' }]}
>
<Input
@@ -163,4 +189,4 @@
);
}
-export default Register;
\ No newline at end of file
+export default Register;
diff --git a/src/feature/auth/authSlice.ts b/src/feature/auth/authSlice.ts
index 01cd4e5..f1ab2a0 100644
--- a/src/feature/auth/authSlice.ts
+++ b/src/feature/auth/authSlice.ts
@@ -1,17 +1,98 @@
-import { createSlice } from "@reduxjs/toolkit";
+import { createAsyncThunk, createSlice, type PayloadAction } from "@reduxjs/toolkit";
import type { AuthState } from "../../store/types";
+import type { LoginRequest } from "../../api/type";
+import authAPI from "../../api/authApi";
const initialState: AuthState = {
token: '',
loading: false,
isAuth: false,
+ error: ''
}
+export const loginUser = createAsyncThunk<
+ {token: string},
+ LoginRequest,
+ { rejectValue: string }
+>(
+ 'auth/login',
+ async (loginRequest: LoginRequest, { rejectWithValue }) => {
+ try {
+ const response = await authAPI.login(loginRequest);
+ if(response.data.code == 0) {
+ return {token: response.data.data};
+ }
+ else
+ return rejectWithValue(response.data.message);
+ } catch {
+ return rejectWithValue('登录失败');
+ }
+ }
+);
+
+export const refreshToken = createAsyncThunk<
+ {token: string},
+ string,
+ { rejectValue: string }
+>(
+
+ 'auth/refresh',
+ async (oldToken: string, { rejectWithValue }) => {
+ try {
+ const response = await authAPI.refreshToken(oldToken);
+ if(response.data.code == 0)
+ return {token: response.data.data};
+ else
+ return rejectWithValue(response.data.message);
+ } catch {
+ return rejectWithValue('刷新失败');
+ }
+ }
+);
+
const authSlice = createSlice({
name: 'auth',
initialState,
- reducers: {},
+ reducers: {
+ logout: (state) => {
+ state.token = '';
+ state.isAuth = false;
+ localStorage.clear()
+ },
+ },extraReducers: (builder) => {
+ // 处理登录的异步操作
+ builder
+ .addCase(loginUser.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(loginUser.fulfilled, (state, action: PayloadAction<{token: string}>) => {
+ state.loading = false;
+ state.token = action.payload.token;
+ state.isAuth = true;
+
+ localStorage.setItem('token', state.token);
+ })
+ .addCase(loginUser.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload ? action.payload : '' // 错误处理
+ })
+
+ // 处理刷新 token 的异步操作
+ .addCase(refreshToken.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(refreshToken.fulfilled, (state, action) => {
+ state.loading = false;
+ state.token = action.payload.token;
+ localStorage.setItem('token', state.token);
+ })
+ .addCase(refreshToken.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload ? action.payload : ''
+ });
+ },
+
});
export default authSlice.reducer;
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index 31a6d39..48b3777 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -8,6 +8,7 @@
import routes from './routes.ts';
import { RouterProvider } from 'react-router';
// 组件库 ant
+import '@ant-design/v5-patch-for-react-19';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
diff --git a/src/store/store.ts b/src/store/store.ts
index ebf1408..5f3841e 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -1,9 +1,10 @@
import { configureStore } from '@reduxjs/toolkit'
-
+import authReducer from "../feature/auth/authSlice"
export const store = configureStore({
reducer: {
-
- }
+ auth: authReducer,
+ },
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(),
})
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
diff --git a/src/store/types.ts b/src/store/types.ts
index 372c089..3e2d796 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -12,4 +12,5 @@
token: string;
loading: boolean;
isAuth: boolean;
+ error: string
}
\ No newline at end of file
diff --git a/src/test/auth/Forget.test.tsx b/src/test/auth/Forget.test.tsx
new file mode 100644
index 0000000..b2ba158
--- /dev/null
+++ b/src/test/auth/Forget.test.tsx
@@ -0,0 +1,24 @@
+import { render, screen } from '@testing-library/react'
+import Forget from '../../feature/auth/Forget'
+import { Provider } from 'react-redux'
+import { store } from '../../store/store'
+import { MemoryRouter } from 'react-router'
+
+describe('Forget Password Page', () => {
+ it('renders forget password form', () => {
+ render(
+ <MemoryRouter>
+ <Provider store={store}>
+ <Forget />
+ </Provider>
+ </MemoryRouter>
+
+ )
+
+ const emailInput = screen.getByPlaceholderText('注册邮箱')
+ const getCodeButton = screen.getByText('获取验证码')
+
+ expect(emailInput).toBeInTheDocument()
+ expect(getCodeButton).toBeInTheDocument()
+ })
+})
\ No newline at end of file
diff --git a/src/test/auth/Login.test.tsx b/src/test/auth/Login.test.tsx
new file mode 100644
index 0000000..ade54e8
--- /dev/null
+++ b/src/test/auth/Login.test.tsx
@@ -0,0 +1,27 @@
+import { render, screen } from '@testing-library/react'
+import Login from '../../feature/auth/Login'
+import { Provider } from 'react-redux'
+import { store } from '../../store/store'
+import { MemoryRouter } from 'react-router'
+
+describe('Login Page', () => {
+ it('renders login form', () => {
+ render(
+ <MemoryRouter>
+ <Provider store={store}>
+ <Login />
+ </Provider>
+ </MemoryRouter>
+
+ )
+
+ const emailInput = screen.getByPlaceholderText('账号(注册邮箱)')
+ const passwordInput = screen.getByPlaceholderText('密码')
+ const loginButton = screen.getByRole('button', { name: /登录/i })
+
+
+ expect(emailInput).toBeInTheDocument()
+ expect(passwordInput).toBeInTheDocument()
+ expect(loginButton).toBeInTheDocument()
+ })
+})
\ No newline at end of file
diff --git a/src/test/auth/Register.test.tsx b/src/test/auth/Register.test.tsx
new file mode 100644
index 0000000..c118a8b
--- /dev/null
+++ b/src/test/auth/Register.test.tsx
@@ -0,0 +1,32 @@
+import { render, screen } from '@testing-library/react'
+import Register from '../../feature/auth/Register'
+import { Provider } from 'react-redux'
+import { store } from '../../store/store'
+import { MemoryRouter } from 'react-router'
+
+describe('Register Page', () => {
+ it('renders register form', () => {
+ render(
+ <MemoryRouter>
+ <Provider store={store}>
+ <Register />
+ </Provider>
+ </MemoryRouter>
+
+ )
+
+ const nameInput = screen.getByPlaceholderText('请输入用户名')
+ const emailInput = screen.getByPlaceholderText('请输入邮箱')
+ const verifyCodeInput = screen.getByPlaceholderText('请输入验证码')
+ const passwordInput = screen.getByPlaceholderText('请输入密码')
+ const confirmPasswordInput = screen.getByPlaceholderText('请确认密码')
+ const registerButton = screen.getByText('注册')
+
+ expect(nameInput).toBeInTheDocument()
+ expect(emailInput).toBeInTheDocument()
+ expect(verifyCodeInput).toBeInTheDocument()
+ expect(passwordInput).toBeInTheDocument()
+ expect(confirmPasswordInput).toBeInTheDocument()
+ expect(registerButton).toBeInTheDocument()
+ })
+})
\ No newline at end of file
diff --git a/src/test/setup.ts b/src/test/setup.ts
new file mode 100644
index 0000000..bb15cf4
--- /dev/null
+++ b/src/test/setup.ts
@@ -0,0 +1,9 @@
+import '@testing-library/jest-dom';
+
+globalThis.matchMedia = globalThis.matchMedia || function() {
+ return {
+ matches: false, // 模拟返回值
+ addListener: () => {}, // 不执行任何操作
+ removeListener: () => {} // 不执行任何操作
+ };
+ };
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 9c8a3c4..cea41fc 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -22,5 +22,5 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
- "include": ["src"]
+ "include": ["src", "src/test/auth"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 8b0f57b..6034831 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,7 +1,23 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import { configDefaults } from 'vitest/config'
-// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ // dev时执行的端口转发
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8080/',
+ changeOrigin: true,
+ rewrite: (path:string) => path.replace(/^\/api/, ''),
+ },
+ },
+ },
+ test: {
+ ...configDefaults,
+ globals:true,
+ environment: 'jsdom', // 确保测试环境为 jsdom
+ setupFiles: './src/test/setup.ts', // 设置你的初始化文件
+ },
})