From 73baf946bd0139e988e2a4e8db08fef77e581ae9 Mon Sep 17 00:00:00 2001 From: javayhu Date: Wed, 20 Aug 2025 22:04:19 +0800 Subject: [PATCH 01/15] chore: add @tanstack/react-query and @tanstack/eslint-plugin-query --- package.json | 2 + pnpm-lock.yaml | 644 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 644 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 30e1716..dc312ab 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@react-email/render": "1.0.5", "@stripe/stripe-js": "^5.6.0", "@tabler/icons-react": "^3.31.0", + "@tanstack/react-query": "^5.85.5", "@tanstack/react-table": "^8.21.2", "@types/canvas-confetti": "^1.9.0", "@vercel/analytics": "^1.5.0", @@ -137,6 +138,7 @@ "devDependencies": { "@biomejs/biome": "1.9.4", "@tailwindcss/postcss": "^4.0.14", + "@tanstack/eslint-plugin-query": "^5.83.1", "@types/mdx": "^2.0.13", "@types/node": "^20.19.0", "@types/pg": "^8.11.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13a6594..def193c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,6 +167,9 @@ importers: '@tabler/icons-react': specifier: ^3.31.0 version: 3.31.0(react@19.0.0) + '@tanstack/react-query': + specifier: ^5.85.5 + version: 5.85.5(react@19.0.0) '@tanstack/react-table': specifier: ^8.21.2 version: 8.21.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -339,6 +342,9 @@ importers: '@tailwindcss/postcss': specifier: ^4.0.14 version: 4.0.14 + '@tanstack/eslint-plugin-query': + specifier: ^5.83.1 + version: 5.83.1(eslint@9.33.0(jiti@2.4.2))(typescript@5.8.3) '@types/mdx': specifier: ^2.0.13 version: 2.0.13 @@ -1364,6 +1370,44 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.33.0': + resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.6.9': resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} @@ -1420,6 +1464,26 @@ packages: peerDependencies: react-hook-form: ^7.55.0 + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3411,6 +3475,19 @@ packages: '@tailwindcss/postcss@4.0.14': resolution: {integrity: sha512-+uIR6KtKhla1XeIanF27KtrfYy+PX+R679v5LxbkmEZlhQe3g8rk+wKj7Xgt++rWGRuFLGMXY80Ek8JNn+kN/g==} + '@tanstack/eslint-plugin-query@5.83.1': + resolution: {integrity: sha512-tdkpPFfzkTksN9BIlT/qjixSAtKrsW6PUVRwdKWaOcag7DrD1vpki3UzzdfMQGDRGeg1Ue1Dg+rcl5FJGembNg==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@tanstack/query-core@5.85.5': + resolution: {integrity: sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==} + + '@tanstack/react-query@5.85.5': + resolution: {integrity: sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==} + peerDependencies: + react: ^18 || ^19 + '@tanstack/react-table@8.21.2': resolution: {integrity: sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==} engines: {node: '>=12'} @@ -3482,6 +3559,9 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -3517,6 +3597,43 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/project-service@8.40.0': + resolution: {integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.40.0': + resolution: {integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.40.0': + resolution: {integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.40.0': + resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.40.0': + resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.40.0': + resolution: {integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.40.0': + resolution: {integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -3591,12 +3708,20 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + ai@5.0.0: resolution: {integrity: sha512-F4jOhOSeiZD8lXpF4l1hRqyM1jbqoLKGVZNxAP467wmQCsWUtElMa3Ki5PrDMq6qvUNC3deUKfERDAsfj7IDlg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3659,6 +3784,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -3689,6 +3817,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001699: resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==} @@ -3777,6 +3909,9 @@ packages: compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -3891,6 +4026,9 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -4136,10 +4274,52 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.33.0: + resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-util-attach-comments@3.0.0: resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} @@ -4164,6 +4344,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -4177,6 +4361,9 @@ packages: fast-deep-equal@2.0.1: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.2.2: resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} engines: {node: '>=6.0.0'} @@ -4185,6 +4372,12 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -4199,6 +4392,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -4206,6 +4403,17 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -4337,6 +4545,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.3.4: resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==} engines: {node: '>=16 || 14 >=14.17'} @@ -4346,6 +4558,10 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -4397,11 +4613,23 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + image-size@2.0.2: resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} engines: {node: '>=16.x'} hasBin: true + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -4490,14 +4718,26 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + knip@5.61.2: resolution: {integrity: sha512-ZBv37zDvZj0/Xwk0e93xSjM3+5bjxgqJ0PH2GlB5tnWV0ktXtmatWLm+dLRUCT/vpO3SdGz2nNAfvVhuItUNcQ==} engines: {node: '>=18.18.0'} @@ -4513,6 +4753,10 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + libpq@1.8.14: resolution: {integrity: sha512-/DDvQCiXP0KBMZ31U2mmURKaxoKt9kNqqgrSO2RuBKS+OJjw5b7uHi5jFoV8zPAUa2TNtq2XfcWL1OWDEyjwlg==} @@ -4580,6 +4824,10 @@ packages: resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -4809,6 +5057,9 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -4855,6 +5106,9 @@ packages: resolution: {integrity: sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==} engines: {node: ^18.0.0 || >=20.0.0} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -4983,6 +5237,10 @@ packages: oniguruma-to-es@4.3.3: resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -4990,12 +5248,28 @@ packages: oxc-resolver@11.2.0: resolution: {integrity: sha512-3iJYyIdDZMDoj0ZSVBrI1gUvPBMkDC4gxonBG+7uqUyK5EslG0mCwnf6qhxK8oEU7jLHjbRBNyzflPSd3uvH7Q==} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} parseley@0.12.1: resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -5128,6 +5402,10 @@ packages: resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} engines: {node: '>=12'} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + prettier@3.4.2: resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} @@ -5154,6 +5432,10 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} @@ -5362,6 +5644,10 @@ packages: resolution: {integrity: sha512-FR22bzMW3VfoyZSBc8ScGo8ShrMWHmWB0G3FrispzWCnYSEEK5M7pyRvZtInKmM/09lsJETKc2q66mX+dXPSmg==} engines: {node: '>=18'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -5517,6 +5803,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + strip-json-comments@5.0.2: resolution: {integrity: sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==} engines: {node: '>=14.16'} @@ -5601,6 +5891,12 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -5612,6 +5908,10 @@ packages: tw-animate-css@1.2.4: resolution: {integrity: sha512-yt+HkJB41NAvOffe4NweJU6fLqAlVx/mBX6XmHRp15kq0JxTtOKaIw8pVSWM1Z+n2nXtyi7cW6C9f0WG/F/QAQ==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + typescript-event-target@1.1.1: resolution: {integrity: sha512-dFSOFBKV6uwaloBCCUhxlD3Pr/P1a/tJdcmPrTXCHlEFD3faj0mztjcGn6VBAhQ0/Bdy8K3VWrrqwbt/ffsYsg==} @@ -5653,6 +5953,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -5735,6 +6038,10 @@ packages: wkt-parser@1.4.0: resolution: {integrity: sha512-qpwO7Ihds/YYDTi1aADFTI1Sm9YC/tTe3SHD24EeIlZxy7Ik6a1b4HOz7jAi0xdUAw487duqpo8OGu+Tf4nwlQ==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5762,6 +6069,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zalgo-promise@1.0.48: resolution: {integrity: sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ==} @@ -6466,6 +6777,50 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.4.2))': + dependencies: + eslint: 9.33.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.33.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + '@floating-ui/core@1.6.9': dependencies: '@floating-ui/utils': 0.2.9 @@ -6538,6 +6893,19 @@ snapshots: '@standard-schema/utils': 0.3.0 react-hook-form: 7.62.0(react@19.0.0) + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -8586,6 +8954,21 @@ snapshots: postcss: 8.5.2 tailwindcss: 4.0.14 + '@tanstack/eslint-plugin-query@5.83.1(eslint@9.33.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.33.0(jiti@2.4.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@tanstack/query-core@5.85.5': {} + + '@tanstack/react-query@5.85.5(react@19.0.0)': + dependencies: + '@tanstack/query-core': 5.85.5 + react: 19.0.0 + '@tanstack/react-table@8.21.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@tanstack/table-core': 8.21.2 @@ -8658,6 +9041,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -8700,6 +9085,58 @@ snapshots: '@types/unist@3.0.3': {} + '@typescript-eslint/project-service@8.40.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.8.3) + '@typescript-eslint/types': 8.40.0 + debug: 4.4.0 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.40.0': + dependencies: + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 + + '@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/types@8.40.0': {} + + '@typescript-eslint/typescript-estree@8.40.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.40.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.8.3) + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.8.3) + eslint: 9.33.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.40.0': + dependencies: + '@typescript-eslint/types': 8.40.0 + eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} '@vercel/analytics@1.5.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': @@ -8730,8 +9167,14 @@ snapshots: dependencies: acorn: 8.14.0 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn@8.14.0: {} + acorn@8.15.0: {} + ai@5.0.0(zod@4.0.17): dependencies: '@ai-sdk/gateway': 1.0.0(zod@4.0.17) @@ -8740,6 +9183,13 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 4.0.17 + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -8815,6 +9265,11 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -8851,6 +9306,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.2.7 + callsites@3.1.0: {} + caniuse-lite@1.0.30001699: {} canvas-confetti@1.9.3: {} @@ -8932,6 +9389,8 @@ snapshots: compute-scroll-into-view@3.1.1: {} + concat-map@0.0.1: {} + convert-source-map@2.0.0: {} cookie@0.7.2: {} @@ -9021,6 +9480,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-is@0.1.4: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -9315,8 +9776,77 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.33.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.33.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-util-attach-comments@3.0.0: dependencies: '@types/estree': 1.0.6 @@ -9354,6 +9884,8 @@ snapshots: dependencies: '@types/estree': 1.0.6 + esutils@2.0.3: {} + eventemitter3@4.0.7: {} eventsource-parser@3.0.3: {} @@ -9362,6 +9894,8 @@ snapshots: fast-deep-equal@2.0.1: {} + fast-deep-equal@3.1.3: {} + fast-equals@5.2.2: {} fast-glob@3.3.3: @@ -9372,6 +9906,10 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -9384,6 +9922,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-uri-to-path@1.0.0: optional: true @@ -9391,6 +9933,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + follow-redirects@1.15.9: {} foreground-child@3.3.0: @@ -9538,6 +10092,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.3.4: dependencies: foreground-child: 3.3.0 @@ -9548,6 +10106,8 @@ snapshots: globals@11.12.0: {} + globals@14.0.0: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -9646,8 +10206,17 @@ snapshots: ieee754@1.2.1: {} + ignore@5.3.2: {} + image-size@2.0.2: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + inherits@2.0.4: {} inline-style-parser@0.2.4: {} @@ -9716,10 +10285,20 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + knip@5.61.2(@types/node@20.19.0)(typescript@5.8.3): dependencies: '@nodelib/fs.walk': 1.2.8 @@ -9742,6 +10321,11 @@ snapshots: leac@0.6.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + libpq@1.8.14: dependencies: bindings: 1.5.0 @@ -9793,6 +10377,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.29.2 lightningcss-win32-x64-msvc: 1.29.2 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.merge@4.6.2: {} lodash@4.17.21: {} @@ -10279,6 +10867,10 @@ snapshots: mimic-fn@2.1.0: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -10310,6 +10902,8 @@ snapshots: nanostores@0.11.4: {} + natural-compare@1.4.0: {} + negotiator@0.6.3: {} negotiator@1.0.0: {} @@ -10424,6 +11018,15 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -10452,6 +11055,18 @@ snapshots: '@oxc-resolver/binding-win32-arm64-msvc': 11.2.0 '@oxc-resolver/binding-win32-x64-msvc': 11.2.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -10467,6 +11082,8 @@ snapshots: leac: 0.6.0 peberminta: 0.9.0 + path-exists@4.0.0: {} + path-key@3.1.1: {} path-scurry@1.11.1: @@ -10609,6 +11226,8 @@ snapshots: postgres@3.4.5: {} + prelude-ls@1.2.1: {} + prettier@3.4.2: {} prettier@3.5.3: {} @@ -10630,6 +11249,8 @@ snapshots: proxy-from-env@1.1.0: {} + punycode@2.3.1: {} + pvtsutils@1.3.6: dependencies: tslib: 2.8.1 @@ -10968,6 +11589,8 @@ snapshots: - react - react-dom + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} restore-cursor@3.1.0: @@ -10999,8 +11622,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.1: - optional: true + semver@7.7.1: {} sharp@0.33.5: dependencies: @@ -11178,6 +11800,8 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-json-comments@3.1.1: {} + strip-json-comments@5.0.2: {} stripe@17.6.0: @@ -11245,6 +11869,10 @@ snapshots: trough@2.2.0: {} + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + tslib@2.8.1: {} tsx@4.19.3: @@ -11256,6 +11884,10 @@ snapshots: tw-animate-css@1.2.4: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + typescript-event-target@1.1.1: {} typescript@5.8.3: {} @@ -11307,6 +11939,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.0.9)(react@19.0.0): dependencies: react: 19.0.0 @@ -11399,6 +12035,8 @@ snapshots: wkt-parser@1.4.0: {} + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -11418,6 +12056,8 @@ snapshots: yallist@3.1.1: {} + yocto-queue@0.1.0: {} + zalgo-promise@1.0.48: {} zod-to-json-schema@3.24.2(zod@3.25.64): From 5431160d62c1c09e495799bbfae85f69698544b7 Mon Sep 17 00:00:00 2001 From: javayhu Date: Wed, 20 Aug 2025 22:37:39 +0800 Subject: [PATCH 02/15] chore: add @tanstack/react-query-devtools --- package.json | 1 + pnpm-lock.yaml | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/package.json b/package.json index dc312ab..a2aba56 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@stripe/stripe-js": "^5.6.0", "@tabler/icons-react": "^3.31.0", "@tanstack/react-query": "^5.85.5", + "@tanstack/react-query-devtools": "^5.85.5", "@tanstack/react-table": "^8.21.2", "@types/canvas-confetti": "^1.9.0", "@vercel/analytics": "^1.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index def193c..1296d35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,6 +170,9 @@ importers: '@tanstack/react-query': specifier: ^5.85.5 version: 5.85.5(react@19.0.0) + '@tanstack/react-query-devtools': + specifier: ^5.85.5 + version: 5.85.5(@tanstack/react-query@5.85.5(react@19.0.0))(react@19.0.0) '@tanstack/react-table': specifier: ^8.21.2 version: 8.21.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -3483,6 +3486,15 @@ packages: '@tanstack/query-core@5.85.5': resolution: {integrity: sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==} + '@tanstack/query-devtools@5.84.0': + resolution: {integrity: sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ==} + + '@tanstack/react-query-devtools@5.85.5': + resolution: {integrity: sha512-6Ol6Q+LxrCZlQR4NoI5181r+ptTwnlPG2t7H9Sp3klxTBhYGunONqcgBn2YKRPsaKiYM8pItpKMdMXMEINntMQ==} + peerDependencies: + '@tanstack/react-query': ^5.85.5 + react: ^18 || ^19 + '@tanstack/react-query@5.85.5': resolution: {integrity: sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==} peerDependencies: @@ -8964,6 +8976,14 @@ snapshots: '@tanstack/query-core@5.85.5': {} + '@tanstack/query-devtools@5.84.0': {} + + '@tanstack/react-query-devtools@5.85.5(@tanstack/react-query@5.85.5(react@19.0.0))(react@19.0.0)': + dependencies: + '@tanstack/query-devtools': 5.84.0 + '@tanstack/react-query': 5.85.5(react@19.0.0) + react: 19.0.0 + '@tanstack/react-query@5.85.5(react@19.0.0)': dependencies: '@tanstack/query-core': 5.85.5 From d59be1044ab6c525abbb3659e8d0fd9be7931bcf Mon Sep 17 00:00:00 2001 From: javayhu Date: Wed, 20 Aug 2025 22:37:52 +0800 Subject: [PATCH 03/15] feat: add QueryProvider to manage React Query client and wrap Providers component --- src/app/[locale]/providers.tsx | 35 +++++++++++--------- src/components/providers/query-provider.tsx | 36 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 src/components/providers/query-provider.tsx diff --git a/src/app/[locale]/providers.tsx b/src/app/[locale]/providers.tsx index 5e85838..c465d47 100644 --- a/src/app/[locale]/providers.tsx +++ b/src/app/[locale]/providers.tsx @@ -3,6 +3,7 @@ import { ActiveThemeProvider } from '@/components/layout/active-theme-provider'; import { CreditsProvider } from '@/components/layout/credits-provider'; import { PaymentProvider } from '@/components/layout/payment-provider'; +import { QueryProvider } from '@/components/providers/query-provider'; import { TooltipProvider } from '@/components/ui/tooltip'; import { websiteConfig } from '@/config/website'; import type { Translations } from 'fumadocs-ui/i18n'; @@ -54,21 +55,23 @@ export function Providers({ children, locale }: ProvidersProps) { }; return ( - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + ); } diff --git a/src/components/providers/query-provider.tsx b/src/components/providers/query-provider.tsx new file mode 100644 index 0000000..c8e15c4 --- /dev/null +++ b/src/components/providers/query-provider.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import type { ReactNode } from 'react'; +import { useState } from 'react'; + +interface QueryProviderProps { + children: ReactNode; +} + +export function QueryProvider({ children }: QueryProviderProps) { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime) + retry: 1, + refetchOnWindowFocus: false, + }, + mutations: { + retry: 1, + }, + }, + }) + ); + + return ( + + {children} + + + ); +} From c00223c79a3530a2ab675b193a4a6c7b55572239 Mon Sep 17 00:00:00 2001 From: javayhu Date: Wed, 20 Aug 2025 22:39:20 +0800 Subject: [PATCH 04/15] refactor: replace server actions with custom hooks for newsletter management and improve loading/error handling --- .../notification/newsletter-form-card.tsx | 144 +++++++----------- src/hooks/use-newsletter.ts | 77 ++++++++++ src/lib/query-client.ts | 15 ++ 3 files changed, 144 insertions(+), 92 deletions(-) create mode 100644 src/hooks/use-newsletter.ts create mode 100644 src/lib/query-client.ts diff --git a/src/components/settings/notification/newsletter-form-card.tsx b/src/components/settings/notification/newsletter-form-card.tsx index 6786c30..5593383 100644 --- a/src/components/settings/notification/newsletter-form-card.tsx +++ b/src/components/settings/notification/newsletter-form-card.tsx @@ -1,8 +1,5 @@ 'use client'; -import { checkNewsletterStatusAction } from '@/actions/check-newsletter-status'; -import { subscribeNewsletterAction } from '@/actions/subscribe-newsletter'; -import { unsubscribeNewsletterAction } from '@/actions/unsubscribe-newsletter'; import { FormError } from '@/components/shared/form-error'; import { Card, @@ -21,12 +18,17 @@ import { } from '@/components/ui/form'; import { Switch } from '@/components/ui/switch'; import { websiteConfig } from '@/config/website'; +import { + useNewsletterStatus, + useSubscribeNewsletter, + useUnsubscribeNewsletter, +} from '@/hooks/use-newsletter'; import { authClient } from '@/lib/auth-client'; import { cn } from '@/lib/utils'; import { zodResolver } from '@hookform/resolvers/zod'; import { Loader2Icon } from 'lucide-react'; import { useTranslations } from 'next-intl'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; @@ -47,12 +49,19 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) { } const t = useTranslations('Dashboard.settings.notification'); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(''); - const [isSubscriptionChecked, setIsSubscriptionChecked] = useState(false); const { data: session } = authClient.useSession(); const currentUser = session?.user; + // TanStack Query hooks + const { + data: newsletterStatus, + isLoading: isStatusLoading, + error: statusError, + } = useNewsletterStatus(currentUser?.email); + + const subscribeMutation = useSubscribeNewsletter(); + const unsubscribeMutation = useUnsubscribeNewsletter(); + // Create a schema for newsletter subscription const formSchema = z.object({ subscribed: z.boolean(), @@ -66,45 +75,12 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) { }, }); - // Check subscription status on component mount + // Update form when newsletter status changes useEffect(() => { - const checkSubscriptionStatus = async () => { - if (currentUser?.email) { - try { - setIsLoading(true); - // Check if the user is already subscribed using server action - const statusResult = await checkNewsletterStatusAction({ - email: currentUser.email, - }); - - if (statusResult?.data?.success) { - const isCurrentlySubscribed = statusResult.data.subscribed; - setIsSubscriptionChecked(isCurrentlySubscribed); - form.setValue('subscribed', isCurrentlySubscribed); - } else { - // Handle error from server action - const errorMessage = statusResult?.data?.error; - if (errorMessage) { - console.error('check subscription status error:', errorMessage); - setError(errorMessage); - } - // Default to not subscribed if there's an error - setIsSubscriptionChecked(false); - form.setValue('subscribed', false); - } - } catch (error) { - console.error('check subscription status error:', error); - // Default to not subscribed if there's an error - setIsSubscriptionChecked(false); - form.setValue('subscribed', false); - } finally { - setIsLoading(false); - } - } - }; - - checkSubscriptionStatus(); - }, [currentUser?.email, form]); + if (newsletterStatus) { + form.setValue('subscribed', newsletterStatus.subscribed); + } + }, [newsletterStatus, form]); // Check if user exists after all hooks are initialized if (!currentUser) { @@ -114,59 +90,27 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) { // Handle checkbox change const handleSubscriptionChange = async (value: boolean) => { if (!currentUser.email) { - setError(t('newsletter.emailRequired')); + toast.error(t('newsletter.emailRequired')); return; } - setIsLoading(true); - setError(''); - try { if (value) { - // Subscribe to newsletter using server action - const subscribeResult = await subscribeNewsletterAction({ - email: currentUser.email, - }); - - if (subscribeResult?.data?.success) { - toast.success(t('newsletter.subscribeSuccess')); - setIsSubscriptionChecked(true); - form.setValue('subscribed', true); - } else { - const errorMessage = - subscribeResult?.data?.error || t('newsletter.subscribeFail'); - toast.error(errorMessage); - setError(errorMessage); - // Reset checkbox if subscription failed - form.setValue('subscribed', false); - } + // Subscribe to newsletter + await subscribeMutation.mutateAsync(currentUser.email); + toast.success(t('newsletter.subscribeSuccess')); } else { - // Unsubscribe from newsletter using server action - const unsubscribeResult = await unsubscribeNewsletterAction({ - email: currentUser.email, - }); - - if (unsubscribeResult?.data?.success) { - toast.success(t('newsletter.unsubscribeSuccess')); - setIsSubscriptionChecked(false); - form.setValue('subscribed', false); - } else { - const errorMessage = - unsubscribeResult?.data?.error || t('newsletter.unsubscribeFail'); - toast.error(errorMessage); - setError(errorMessage); - // Reset checkbox if unsubscription failed - form.setValue('subscribed', true); - } + // Unsubscribe from newsletter + await unsubscribeMutation.mutateAsync(currentUser.email); + toast.success(t('newsletter.unsubscribeSuccess')); } } catch (error) { console.error('newsletter subscription error:', error); - setError(t('newsletter.error')); - toast.error(t('newsletter.error')); + const errorMessage = + error instanceof Error ? error.message : t('newsletter.error'); + toast.error(errorMessage); // Reset form to previous state on error - form.setValue('subscribed', isSubscriptionChecked); - } finally { - setIsLoading(false); + form.setValue('subscribed', newsletterStatus?.subscribed || false); } }; @@ -193,7 +137,9 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
- {isLoading && ( + {(isStatusLoading || + subscribeMutation.isPending || + unsubscribeMutation.isPending) && ( )}
@@ -211,7 +165,13 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) { )} /> - +

diff --git a/src/hooks/use-newsletter.ts b/src/hooks/use-newsletter.ts new file mode 100644 index 0000000..d4f80ce --- /dev/null +++ b/src/hooks/use-newsletter.ts @@ -0,0 +1,77 @@ +import { checkNewsletterStatusAction } from '@/actions/check-newsletter-status'; +import { subscribeNewsletterAction } from '@/actions/subscribe-newsletter'; +import { unsubscribeNewsletterAction } from '@/actions/unsubscribe-newsletter'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +// Query keys +export const newsletterKeys = { + all: ['newsletter'] as const, + status: (email: string) => [...newsletterKeys.all, 'status', email] as const, +}; + +// Hook to check newsletter subscription status +export function useNewsletterStatus(email: string | undefined) { + return useQuery({ + queryKey: newsletterKeys.status(email || ''), + queryFn: async () => { + if (!email) { + throw new Error('Email is required'); + } + const result = await checkNewsletterStatusAction({ email }); + if (!result?.data?.success) { + throw new Error( + result?.data?.error || 'Failed to check newsletter status' + ); + } + return result.data; + }, + enabled: !!email, + staleTime: 5 * 60 * 1000, // 5 minutes + }); +} + +// Hook to subscribe to newsletter +export function useSubscribeNewsletter() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (email: string) => { + const result = await subscribeNewsletterAction({ email }); + if (!result?.data?.success) { + throw new Error( + result?.data?.error || 'Failed to subscribe to newsletter' + ); + } + return result.data; + }, + onSuccess: (_, email) => { + // Invalidate and refetch the newsletter status + queryClient.invalidateQueries({ + queryKey: newsletterKeys.status(email), + }); + }, + }); +} + +// Hook to unsubscribe from newsletter +export function useUnsubscribeNewsletter() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (email: string) => { + const result = await unsubscribeNewsletterAction({ email }); + if (!result?.data?.success) { + throw new Error( + result?.data?.error || 'Failed to unsubscribe from newsletter' + ); + } + return result.data; + }, + onSuccess: (_, email) => { + // Invalidate and refetch the newsletter status + queryClient.invalidateQueries({ + queryKey: newsletterKeys.status(email), + }); + }, + }); +} diff --git a/src/lib/query-client.ts b/src/lib/query-client.ts new file mode 100644 index 0000000..bb0486e --- /dev/null +++ b/src/lib/query-client.ts @@ -0,0 +1,15 @@ +import { QueryClient } from '@tanstack/react-query'; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime) + retry: 1, + refetchOnWindowFocus: false, + }, + mutations: { + retry: 1, + }, + }, +}); From d153ca655e4f19204f6e73a84d052e14a2f40c24 Mon Sep 17 00:00:00 2001 From: javayhu Date: Wed, 20 Aug 2025 23:52:06 +0800 Subject: [PATCH 05/15] refactor: implement custom hooks for user management and ban/unban user --- src/components/admin/user-detail-viewer.tsx | 62 +++++++------- src/components/admin/users-page.tsx | 70 ++++------------ src/hooks/use-users.ts | 93 +++++++++++++++++++++ 3 files changed, 138 insertions(+), 87 deletions(-) create mode 100644 src/hooks/use-users.ts diff --git a/src/components/admin/user-detail-viewer.tsx b/src/components/admin/user-detail-viewer.tsx index 3936f58..85a0b3a 100644 --- a/src/components/admin/user-detail-viewer.tsx +++ b/src/components/admin/user-detail-viewer.tsx @@ -6,7 +6,6 @@ import { Drawer, DrawerClose, DrawerContent, - DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, @@ -21,13 +20,12 @@ import { import { Separator } from '@/components/ui/separator'; import { Textarea } from '@/components/ui/textarea'; import { useIsMobile } from '@/hooks/use-mobile'; -import { authClient } from '@/lib/auth-client'; +import { useBanUser, useUnbanUser } from '@/hooks/use-users'; import type { User } from '@/lib/auth-types'; import { isDemoWebsite } from '@/lib/demo'; import { formatDate } from '@/lib/formatter'; import { getStripeDashboardCustomerUrl } from '@/lib/urls/urls'; import { cn } from '@/lib/utils'; -import { useUsersStore } from '@/stores/users-store'; import { CalendarIcon, Loader2Icon, @@ -47,11 +45,13 @@ interface UserDetailViewerProps { export function UserDetailViewer({ user }: UserDetailViewerProps) { const t = useTranslations('Dashboard.admin.users'); const isMobile = useIsMobile(); - const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); const [banReason, setBanReason] = useState(t('ban.defaultReason')); const [banExpiresAt, setBanExpiresAt] = useState(); - const triggerRefresh = useUsersStore((state) => state.triggerRefresh); + + // TanStack Query mutations + const banUserMutation = useBanUser(); + const unbanUserMutation = useUnbanUser(); // show fake data in demo website const isDemo = isDemoWebsite(); @@ -67,11 +67,10 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) { return; } - setIsLoading(true); setError(''); try { - await authClient.admin.banUser({ + await banUserMutation.mutateAsync({ userId: user.id, banReason, banExpiresIn: banExpiresAt @@ -83,15 +82,11 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) { // Reset form setBanReason(''); setBanExpiresAt(undefined); - // Trigger refresh - triggerRefresh(); } catch (err) { const error = err as Error; console.error('Failed to ban user:', error); setError(error.message || t('ban.error')); toast.error(error.message || t('ban.error')); - } finally { - setIsLoading(false); } }; @@ -101,24 +96,19 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) { return; } - setIsLoading(true); setError(''); try { - await authClient.admin.unbanUser({ + await unbanUserMutation.mutateAsync({ userId: user.id, }); toast.success(t('unban.success')); - // Trigger refresh - triggerRefresh(); } catch (err) { const error = err as Error; console.error('Failed to unban user:', error); setError(error.message || t('unban.error')); toast.error(error.message || t('unban.error')); - } finally { - setIsLoading(false); } }; @@ -166,7 +156,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) { {user.role === 'admin' ? t('admin') : t('user')} {/* email verified */} - + {/* {user.emailVerified ? ( ) : ( @@ -175,7 +165,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) { {user.emailVerified ? t('email.verified') : t('email.unverified')} - + */} {/* user banned */}

@@ -196,15 +186,23 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) { {t('columns.email')}: - { - navigator.clipboard.writeText(user.email!); - toast.success(t('emailCopied')); - }} - > - {user.email} - +
+ { + navigator.clipboard.writeText(user.email); + toast.success(t('emailCopied')); + }} + > + {user.emailVerified ? ( + + ) : ( + + )} + {user.email} + +
)} @@ -256,10 +254,10 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {