From 6d6d300203750684c1c6b07e5cddeb40f37a2170 Mon Sep 17 00:00:00 2001 From: Eamon <17516219072@163.com> Date: Thu, 3 Sep 2026 14:37:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=AA=E4=BA=BA=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 3 + README.md | 16 +- bun.lock | 74 +- package.json | 2 + src/admin/cashOut/page.tsx | 1 - src/admin/dashboard/mock.ts | 24 - src/admin/dashboard/page.tsx | 739 ++++++++++-------- src/admin/funds/page.tsx | 2 - src/admin/httpRecharge/page.tsx | 1 - src/admin/nav-menu.tsx | 2 +- src/api/agreement.ts | 82 ++ src/api/dashboard.ts | 11 - src/api/http.ts | 44 +- .../composites/dialogs/agreementDialog.tsx | 216 +++++ .../composites/dialogs/editInfoDialog.tsx | 2 +- .../composites/dialogs/realNameDialog.tsx | 213 +++++ src/components/qrCodeImage.tsx | 48 ++ src/home/header.tsx | 2 +- src/lib/models/dashboard.ts | 11 - vite.config.ts | 20 + 20 files changed, 1135 insertions(+), 378 deletions(-) create mode 100644 .env.development create mode 100644 src/api/agreement.ts create mode 100644 src/components/composites/dialogs/agreementDialog.tsx create mode 100644 src/components/composites/dialogs/realNameDialog.tsx create mode 100644 src/components/qrCodeImage.tsx diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..77d93a0 --- /dev/null +++ b/.env.development @@ -0,0 +1,3 @@ +# 开发环境:php-api 请求走同源 vite proxy(见 vite.config.ts) +# 生产环境使用 .env 中的正式地址 +VITE_PHP_API_BASE_URL= diff --git a/README.md b/README.md index 0b629fe..cd0b496 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# React + TypeScript + Vite -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +### 首页免费试用按钮做权限校验 -Currently, two official plugins are available: +### 个人中心的二维码生成 使用qrcode.js 生成、hover 放大等 -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) +### 访问权限地方共享状态的封装与使用 -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). +### 实名机制 + +### 联系客服 +### dockerfile 文件 +### 提取IP流程 \ No newline at end of file diff --git a/bun.lock b/bun.lock index c395492..9beb093 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,7 @@ "date-fns": "^4.4.0", "lucide-react": "^1.17.0", "next-themes": "^0.4.6", + "qrcode": "^1.5.4", "radix-ui": "^1.5.0", "react": "^19.2.6", "react-dom": "^19.2.6", @@ -30,6 +31,7 @@ "devDependencies": { "@biomejs/biome": "2.4.16", "@types/node": "^25.9.2", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", @@ -436,6 +438,8 @@ "@types/node": ["@types/node@25.9.2", "https://registry.npmmirror.com/@types/node/-/node-25.9.2.tgz", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], + "@types/react": ["@types/react@19.2.17", "https://registry.npmmirror.com/@types/react/-/react-19.2.17.tgz", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], "@types/react-dom": ["@types/react-dom@19.2.3", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], @@ -464,6 +468,8 @@ "ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "argparse": ["argparse@2.0.1", "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "aria-hidden": ["aria-hidden@1.2.6", "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], @@ -496,6 +502,8 @@ "callsites": ["callsites@3.1.0", "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + "caniuse-lite": ["caniuse-lite@1.0.30001797", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="], "ccount": ["ccount@2.0.1", "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -516,12 +524,18 @@ "cli-spinners": ["cli-spinners@2.9.2", "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "clsx": ["clsx@2.1.1", "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "code-block-writer": ["code-block-writer@13.0.3", "https://registry.npmmirror.com/code-block-writer/-/code-block-writer-13.0.3.tgz", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], "collapse-white-space": ["collapse-white-space@2.1.0", "https://registry.npmmirror.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@14.0.3", "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], @@ -552,6 +566,8 @@ "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "dedent": ["dedent@1.7.2", "https://registry.npmmirror.com/dedent/-/dedent-1.7.2.tgz", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], @@ -576,6 +592,8 @@ "diff": ["diff@8.0.4", "https://registry.npmmirror.com/diff/-/diff-8.0.4.tgz", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], + "dotenv": ["dotenv@17.4.2", "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -586,7 +604,7 @@ "electron-to-chromium": ["electron-to-chromium@1.5.371", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz", {}, "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w=="], - "emoji-regex": ["emoji-regex@10.6.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "encodeurl": ["encodeurl@2.0.0", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -660,6 +678,8 @@ "finalhandler": ["finalhandler@2.1.1", "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], "forwarded": ["forwarded@0.2.0", "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -676,6 +696,8 @@ "gensync": ["gensync@1.0.0-beta.2", "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["get-east-asian-width@1.6.0", "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -740,6 +762,8 @@ "is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-hexadecimal": ["is-hexadecimal@2.0.1", "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], @@ -816,6 +840,8 @@ "lines-and-columns": ["lines-and-columns@1.2.4", "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "log-symbols": ["log-symbols@6.0.0", "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], "longest-streak": ["longest-streak@3.1.0", "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -958,6 +984,12 @@ "ora": ["ora@8.2.0", "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "parent-module": ["parent-module@1.0.1", "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-entities": ["parse-entities@4.0.2", "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -970,6 +1002,8 @@ "path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-to-regexp": ["path-to-regexp@8.4.2", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], @@ -980,6 +1014,8 @@ "pkce-challenge": ["pkce-challenge@5.0.1", "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + "postcss": ["postcss@8.5.15", "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], "postcss-selector-parser": ["postcss-selector-parser@7.1.2", "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.2.tgz", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Wjvt4scRFouioIInHf51IFNP4ltJ2EngJM+cZPGiqbKetBfmP3vpdPV8ID2S6JS6/jdo74N8+aEYH9lQr2C6sA=="], @@ -994,6 +1030,8 @@ "proxy-addr": ["proxy-addr@2.0.7", "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], + "qs": ["qs@6.15.2", "https://registry.npmmirror.com/qs/-/qs-6.15.2.tgz", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="], "queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -1038,8 +1076,12 @@ "remark-rehype": ["remark-rehype@11.1.2", "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + "resolve-from": ["resolve-from@4.0.0", "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "restore-cursor": ["restore-cursor@5.1.0", "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -1066,6 +1108,8 @@ "serve-static": ["serve-static@2.2.1", "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], "setprototypeof": ["setprototypeof@1.2.0", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], @@ -1100,7 +1144,7 @@ "stdin-discarder": ["stdin-discarder@0.2.2", "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], - "string-width": ["string-width@7.2.0", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "stringify-entities": ["stringify-entities@4.0.4", "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], @@ -1190,12 +1234,22 @@ "which": ["which@4.0.0", "https://registry.npmmirror.com/which/-/which-4.0.0.tgz", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "wsl-utils": ["wsl-utils@0.3.1", "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.3.1.tgz", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + "yallist": ["yallist@3.1.1", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + "yocto-spinner": ["yocto-spinner@1.2.0", "https://registry.npmmirror.com/yocto-spinner/-/yocto-spinner-1.2.0.tgz", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw=="], "yoctocolors": ["yoctocolors@2.1.2", "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.2.tgz", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], @@ -1226,6 +1280,8 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "cliui/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "cross-spawn/which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "enquirer/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1238,6 +1294,8 @@ "npm-run-path/path-key": ["path-key@4.0.0", "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "ora/string-width": ["string-width@7.2.0", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "prompts/kleur": ["kleur@3.0.3", "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], @@ -1248,8 +1306,12 @@ "shadcn/zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "string-width/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "type-is/content-type": ["content-type@2.0.0", "https://registry.npmmirror.com/content-type/-/content-type-2.0.0.tgz", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], @@ -1262,8 +1324,16 @@ "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/package.json b/package.json index 3f0757b..8e4a25f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "date-fns": "^4.4.0", "lucide-react": "^1.17.0", "next-themes": "^0.4.6", + "qrcode": "^1.5.4", "radix-ui": "^1.5.0", "react": "^19.2.6", "react-dom": "^19.2.6", @@ -34,6 +35,7 @@ "devDependencies": { "@biomejs/biome": "2.4.16", "@types/node": "^25.9.2", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", diff --git a/src/admin/cashOut/page.tsx b/src/admin/cashOut/page.tsx index c00626d..558b329 100644 --- a/src/admin/cashOut/page.tsx +++ b/src/admin/cashOut/page.tsx @@ -2,7 +2,6 @@ import DataTable from "@/components/data-table" import Page from "@/components/page" import type { CashOutItem } from "@/lib/models/cashOut" - export default function CashOutPage() { return ( diff --git a/src/admin/dashboard/mock.ts b/src/admin/dashboard/mock.ts index f0253da..2f5b39a 100644 --- a/src/admin/dashboard/mock.ts +++ b/src/admin/dashboard/mock.ts @@ -3,7 +3,6 @@ import type { ConsumptionStats, DashboardBalance, IpAccountStats, - RecentOrderItem, } from "@/lib/models/dashboard" // TODO: 后端仪表盘接口就绪后删除(见 src/api/dashboard.ts) @@ -21,29 +20,6 @@ export const MOCK_ACCOUNT_INFO: AccountInfo = { taobao: "123456123", } -export const MOCK_RECENT_ORDERS: RecentOrderItem[] = [ - { - date: "2025.04.01", - product: "极光IP", - plan: "天卡(尊享)", - price: 3.0, - connections: 1, - account: "jx1234", - orderAmount: 3.0, - paidAmount: 3.0, - }, - { - date: "2025.04.01", - product: "极光IP", - plan: "天卡(尊享)", - price: 3.0, - connections: 1, - account: "jx1234", - orderAmount: 3.0, - paidAmount: 3.0, - }, -] - export const MOCK_CONSUMPTION: ConsumptionStats = { today: 30.0, month: 30.0, diff --git a/src/admin/dashboard/page.tsx b/src/admin/dashboard/page.tsx index a3a8ac6..b45c2a0 100644 --- a/src/admin/dashboard/page.tsx +++ b/src/admin/dashboard/page.tsx @@ -1,11 +1,22 @@ -import { useState } from "react" +import { Check } from "lucide-react" +import { type ReactNode, useEffect, useState } from "react" +import { fetchAgreementStatus, fetchRealNameStatus } from "@/api/agreement" +import { getUserInfo } from "@/api/token" +import AgreementDialog from "@/components/composites/dialogs/agreementDialog" import { EditInfoDialog, type EditInfoValues, } from "@/components/composites/dialogs/editInfoDialog" -import DataTable from "@/components/data-table" +import RealNameDialog from "@/components/composites/dialogs/realNameDialog" import Page from "@/components/page" +import { QrCodeImage } from "@/components/qrCodeImage" import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" import { cn } from "@/lib/utils" import b3 from "../assets/b3.webp" import b5 from "../assets/b5.webp" @@ -23,365 +34,465 @@ import { MOCK_BALANCE, MOCK_CONSUMPTION, MOCK_IP_STATS, - MOCK_RECENT_ORDERS, } from "./mock" +const fmt = (n: number) => n.toFixed(2) + +const MANAGER_QR_URLS: Record = { + 1029: "https://work.weixin.qq.com/u/vc887a78c8a042ee8e?v=4.1.8.133653", + 1030: "https://work.weixin.qq.com/u/vc83c509032e079ac3?v=4.1.8.23932", + 1017: "https://work.weixin.qq.com/u/vc7e4f48bc9e52e3d0?v=4.1.8.133653", +} + +function readUserInfo() { + const raw = getUserInfo>() + const info = (raw ?? {}) as Record + const phone = String( + info.Phone ?? + info.LoginCode ?? + info.phone ?? + info.logincode ?? + info.Mobile ?? + info.mobile ?? + "", + ) + const managerId = Number(info.ManagerId ?? info.managerId ?? 0) + return { + phone, + managerQrUrl: + Number.isFinite(managerId) && MANAGER_QR_URLS[managerId] + ? MANAGER_QR_URLS[managerId] + : "", + } +} export default function DashboardPage() { // ========== 数据定义(待接口就绪后改为 useEffect 拉取) ========== const balanceData = MOCK_BALANCE const accountInfo = MOCK_ACCOUNT_INFO - const recentOrders = MOCK_RECENT_ORDERS const consumptionData = MOCK_CONSUMPTION const ipAccountData = MOCK_IP_STATS + const { phone, managerQrUrl } = readUserInfo() + + // 实名认证状态(php-api /agent/index/isverify)、网络安全协议签署状态 + // (php-api /tencent/index/get_is_upload) + const [verified, setVerified] = useState(null) + const [agreementSigned, setAgreementSigned] = useState(null) + + const refreshVerifyState = () => { + if (!phone) return + void fetchRealNameStatus(phone).then(res => { + if (res.success) setVerified(res.data) + }) + } + + const refreshAgreementState = () => { + void fetchAgreementStatus().then(res => { + if (res.success) setAgreementSigned(res.data) + }) + } + + useEffect(() => { + if (phone) { + void fetchRealNameStatus(phone).then(res => { + if (res.success) setVerified(res.data) + }) + } + void fetchAgreementStatus().then(res => { + if (res.success) setAgreementSigned(res.data) + }) + }, [phone]) + const [editDialogOpen, setEditDialogOpen] = useState(false) + const [realNameOpen, setRealNameOpen] = useState(false) + const [agreementOpen, setAgreementOpen] = useState(false) + const [managerQrOpen, setManagerQrOpen] = useState(false) const handleSaveInfo = (data: EditInfoValues) => { // TODO: 接入 src/api/user.ts updateUserProfile void data } - return ( - -
- {/* 第一个卡片 */} -
-
-
- - 可用余额(元) - -
-
-

- {balanceData.balance.toFixed(2)} -

-
- - -
-
-
- banner -
+ // 未返回状态(请求中/失败/无手机号)时默认按「未实名/未签署」展示, + // 接口成功后自动更新为真实状态 + const verifyBadge = (() => { + if (verified === true) { + return ( + + + 已认证 + + ) + } + return ( + + ) + })() - {/* 第二个卡片 */} -
-
-
-

可用H币(个)

+ const agreementAction = (mobile = false) => { + const className = mobile + ? "mt-3 inline-flex items-center gap-1 self-start text-xs sm:hidden" + : "hidden items-center gap-1 text-xs sm:inline-flex" + if (agreementSigned === true) { + return ( + + 签署网络安全协议 + + + ) + } + return ( + + ) + } + + return ( + +
+
+
+
+

账户信息

+ {verifyBadge}
-
-

- {balanceData.hCoin.toFixed(2)} -

+
+ {agreementAction()}
- banner2 + +
+
+
+

+ 扫二维码联系 +
+ 我的客户经理 +

+
+ + {managerQrUrl && ( +
+ +
+ )} +
+
+ +
+
+

手机号/用户名

+

+ {phone || accountInfo.phone} +

+
+
+

密码

+
+ ******** + +
+
+
+
+ +
+
+ + + + +
+ {agreementAction(true)} +
+
+ + + + + + + + +
-
-

账号信息

-
- {/* 头像/二维码区域 */} -
- - {accountInfo.phone} - - -
- 账号二维码 +
+
+

消费信息

+
+ +
+
+ + + + + +
-
- {/* 信息列表 */} -
-
- 密码 - - ******** - -
-
- QQ - - {accountInfo.qq || "未绑定"} - -
-
- 微信号 - - {accountInfo.wechat || "未绑定"} - -
-
- 淘宝 - - {accountInfo.taobao || "未绑定"} - -
-
- 邮箱 - - {accountInfo.email || "未绑定"} - +
+ 本年消费 +
+

+ {fmt(consumptionData.year)} +

+

本年消费

+
- -
- -
-
- - {/* 可以优化掉不要 */} -
-

最近订单

-
- row.original.price.toFixed(2), - }, - { accessorKey: "connections", header: "总连接数" }, - { accessorKey: "account", header: "账号" }, - { - accessorKey: "orderAmount", - header: "订单金额(元)", - cell: ({ row }) => row.original.orderAmount.toFixed(2), - }, - { - accessorKey: "paidAmount", - header: "实付金额(元)", - cell: ({ row }) => row.original.paidAmount.toFixed(2), - }, - ]} - /> -
-
- - {/* ====== 消费信息 + IP账号 ====== */} -
-
-

消费信息

-
-
- {/* 今日消费 */} -
- 今日消费 -
-

今日消费

-

- {consumptionData.today} -

-
+
+
+

IP账号

- - {/* 当月消费 */} -
- 当月消费 + -
-

当月消费

-

- {consumptionData.month} -

-
-
- - {/* 今日退款 */} -
- 今日退款 -
-

今日退款

-

- {consumptionData.todayRefund} -

-
-
- - {/* 本年消费 */} -
- 本年消费 -
-

本年消费

-

- {consumptionData.year} -

-
-
- - {/* 今日充值 */} -
- 今日充值 -
-

今日充值

-

- {consumptionData.todayRecharge} -

-
-
- - {/* 当月充值 */} -
- 当月充值 -
-

当月充值

-

- {consumptionData.monthRecharge} -

-
-
- - {/* 当月退款 */} -
- 当月退款 -
-

当月退款

-

- {consumptionData.monthRefund} -

-
-
- - {/* ====== IP账号 ====== */} -
-

IP账号

-
- {/* 使用中 */} -
-
- 使用中 -
-

{ipAccountData.using}

-

使用中

-
- - {/* 总个数 */} -
-
- 总个数 -
-

{ipAccountData.total}

-

总个数

-
- - {/* 已过期 */} -
-
- 已过期 -
-

{ipAccountData.expired}

-

已过期

-
-
-
+
+ + + + + + 我的客户经理 + +
+
+ +
+

请扫码添加

+
+
+
) } + +function InfoRow({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value || "--"}
+
+ ) +} + +function BalanceCard(props: { + label: string + amount: string + image: string + imageAlt: string + gradientClassName?: string + children?: ReactNode +}) { + return ( +
+
+

{props.label}

+ {props.imageAlt} +
+
+

+ {props.amount} +

+
{props.children}
+
+
+ ) +} + +function StatTile(props: { + icon: string + alt: string + label: string + value: string +}) { + return ( +
+ {props.alt} +
+

{props.label}

+

+ {props.value} +

+
+
+ ) +} + +function IpStat(props: { + icon: string + alt: string + value: number + label: string + chipClassName?: string +}) { + return ( +
+
+ {props.alt} +
+

{props.value}

+

{props.label}

+
+ ) +} diff --git a/src/admin/funds/page.tsx b/src/admin/funds/page.tsx index 3389436..3ea4955 100644 --- a/src/admin/funds/page.tsx +++ b/src/admin/funds/page.tsx @@ -1,8 +1,6 @@ import DataTable from "@/components/data-table" import Page from "@/components/page" - - export default function FundsPage() { return ( diff --git a/src/admin/httpRecharge/page.tsx b/src/admin/httpRecharge/page.tsx index cb2e876..0942d57 100644 --- a/src/admin/httpRecharge/page.tsx +++ b/src/admin/httpRecharge/page.tsx @@ -2,7 +2,6 @@ import DataTable from "@/components/data-table" import Page from "@/components/page" import type { HttpRechargeItem } from "@/lib/models/httpRecharge" - export default function HttpRechargePage() { return ( diff --git a/src/admin/nav-menu.tsx b/src/admin/nav-menu.tsx index 7ed9432..336f8a2 100644 --- a/src/admin/nav-menu.tsx +++ b/src/admin/nav-menu.tsx @@ -52,7 +52,7 @@ function AdminMenuItem({ const [isOpen, setIsOpen] = useState(false) const [pos, setPos] = useState({ top: 0, left: 0 }) const triggerRef = useRef(null) - const timeoutRef = useRef(null) + const timeoutRef = useRef | null>(null) const updatePosition = useCallback(() => { if (triggerRef.current) { diff --git a/src/api/agreement.ts b/src/api/agreement.ts new file mode 100644 index 0000000..7b89cc5 --- /dev/null +++ b/src/api/agreement.ts @@ -0,0 +1,82 @@ +import type { ApiResponse } from "@/lib/api" +import { phpForm, phpGet, phpPost } from "./http" + +// ==================== 实名认证 / 网络安全协议(php-api) ==================== +// 接口来源:参考 juipnet 老项目 Host/Views/User/Index.cshtml、upload.cshtml + +export type RealNameVerifyResult = { + // 10000:进入微信扫码扫脸阶段(返回二维码 url) + // 20000:已认证成功 + // 30000:该身份已被其他账号认证 + // 其他:姓名与身份证不匹配 + code: number + url?: string + msg?: string +} + +export async function fetchRealNameStatus( + phone: string, +): Promise> { + const res = await phpGet<{ is_verify?: string | number }>( + `/agent/index/isverify?phone=${encodeURIComponent(phone)}`, + ) + if (!res.success) return res + return { + success: true, + data: String(res.data?.is_verify) === "1", + } +} + +export async function submitRealName(props: { + idCode: string + name: string +}): Promise> { + const res = await phpPost>("/realname/index/verify", { + cookie: document.cookie, + id_code: props.idCode, + name: props.name, + }) + if (!res.success) return res + const raw = (res.data ?? {}) as Record + return { + success: true, + data: { + code: Number(raw.Code ?? raw.code ?? 0), + url: typeof raw.url === "string" ? raw.url : undefined, + msg: typeof raw.msg === "string" ? raw.msg : undefined, + }, + } +} + +export async function fetchAgreementStatus(): Promise> { + const res = await phpPost>( + "/tencent/index/get_is_upload", + { cookie: document.cookie }, + ) + if (!res.success) return res + return { + success: true, + data: Number(res.data?.code ?? 0) === 1, + } +} + +export async function uploadAgreement(props: { + describe: string + file: File +}): Promise> { + const form = new FormData() + form.append("cookie", document.cookie) + form.append("describe", props.describe) + form.append("status", "true") + form.append("xieyi", props.file) + + const res = await phpForm>( + "/tencent/index/upload", + form, + ) + if (!res.success) return res + return { + success: true, + data: { msg: typeof res.data?.msg === "string" ? res.data.msg : undefined }, + } +} diff --git a/src/api/dashboard.ts b/src/api/dashboard.ts index 0a836c8..b1bf916 100644 --- a/src/api/dashboard.ts +++ b/src/api/dashboard.ts @@ -1,5 +1,4 @@ import type { ApiResponse } from "@/lib/api" -import type { OrderItem, OrderQueryParams } from "@/lib/models/order" import { callByUser } from "./base" import { fetchHttpBalance } from "./http" @@ -20,16 +19,6 @@ export async function fetchDashboardConsumption( ) } -export async function fetchDashboardRecentOrders( - params: OrderQueryParams, -): Promise> { - return await callByUser( - "/api/course/v1/order/Page", - { ...params, PageIndex: 1, PageSize: 10 }, - "GET", - ) -} - // TODO: 账号信息/IP 统计接口路径待后端确认 export async function fetchDashboardAccountInfo(): Promise< ApiResponse> diff --git a/src/api/http.ts b/src/api/http.ts index 0dde00e..e65e971 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -48,7 +48,8 @@ export async function phpFetch( try { const response = await fetch(`${PHP_API_BASE_URL}${path}`, { method, - headers: body !== undefined ? { "Content-Type": "application/json" } : undefined, + headers: + body !== undefined ? { "Content-Type": "application/json" } : undefined, credentials, body: body !== undefined ? JSON.stringify(body) : undefined, signal: controller.signal, @@ -87,6 +88,47 @@ export async function phpPost( return phpFetch("POST", path, body, { credentials: "include" }) } +// multipart 上传(手持身份证等文件场景),不设 Content-Type,由浏览器自动补全 boundary +export async function phpForm( + path: string, + form: FormData, +): Promise> { + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT) + try { + const response = await fetch(`${PHP_API_BASE_URL}${path}`, { + method: "POST", + credentials: "include", + body: form, + signal: controller.signal, + }) + if (!response.ok) { + return { + success: false, + status: response.status, + message: "请求失败", + } + } + const data = (await response.json()) as T + return { success: true, data } + } catch (e) { + if (controller.signal.aborted) { + return { + success: false, + status: 408, + message: "请求超时,请稍后重试", + } + } + return { + success: false, + status: 500, + message: (e as Error).message || "网络错误", + } + } finally { + clearTimeout(timer) + } +} + export async function phpGet( path: string, ): Promise> { diff --git a/src/components/composites/dialogs/agreementDialog.tsx b/src/components/composites/dialogs/agreementDialog.tsx new file mode 100644 index 0000000..98b292f --- /dev/null +++ b/src/components/composites/dialogs/agreementDialog.tsx @@ -0,0 +1,216 @@ +import { useRef, useState } from "react" +import { toast } from "sonner" +import { uploadAgreement } from "@/api/agreement" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export default function AgreementDialog({ + open, + onOpenChange, + onSigned, +}: { + open: boolean + onOpenChange: (open: boolean) => void + onSigned: () => void +}) { + const [describe, setDescribe] = useState("") + const [file, setFile] = useState(null) + const [agreed, setAgreed] = useState(false) + const [submitting, setSubmitting] = useState(false) + const fileInputRef = useRef(null) + + const handleOpenChange = (next: boolean) => { + onOpenChange(next) + if (!next) { + setDescribe("") + setFile(null) + setAgreed(false) + if (fileInputRef.current) fileInputRef.current.value = "" + } + } + + const handleSubmit = async () => { + if (!agreed) { + toast.error("请勾选同意协议!") + return + } + if (describe.trim().length < 2) { + toast.error("业务描述必须填写!") + return + } + if (!file) { + toast.error("手持身份证必须上传!") + return + } + + setSubmitting(true) + const res = await uploadAgreement({ describe: describe.trim(), file }) + setSubmitting(false) + + if (!res.success) { + toast.error(res.message) + return + } + toast.success(res.data.msg || "提交成功,请等待客服审核") + handleOpenChange(false) + onSigned() + } + + return ( + + + + + 签署网络安全协议 + + + +

+ 您可通过以下要求勾选同意网络安全协议书后联系客服审核,然后解除微信QQ等屏蔽,新增的账号不会自动解除,记得联系客服哦 +

+

+ 注:解除屏蔽后,qq和微信也仅可用于游戏之类的登录授权,不可用于聊天添加好友等聊天软件内部的功能,系统有严密日志,如有此类情况,账号立即封停,且不予退款,若触犯相关法律将上报给公安部门,请您知晓! +

+ +
+ + setDescribe(e.target.value)} + placeholder="请描述业务用途" + className="h-11 bg-gray-50 border-gray-200" + /> +
+ +
+ +
+ setFile(e.target.files?.[0] ?? null)} + className="block w-full text-sm text-gray-500 file:mr-3 file:rounded-md file:border-0 file:bg-blue-50 file:px-3 file:py-2 file:text-sm file:font-medium file:text-blue-600 hover:file:bg-blue-100" + /> +
+

手持身份证的半身照片

+
+ +
+ +
+

+ 网络信息安全承诺书 +

+

+ 本单位(包含个人)郑重承诺遵守本承诺书的有关条款,如有违反本承诺书有关条款的行为,本单位(包含个人)承担由此带来的一切民事、行政和刑事责任。 +

+

+ 一、本单位(包含个人)承诺遵守《中华人民共和国计算机信息系统安全保护条例》和《计算机信息网络国际联网安全保护管理办法》及有关法律、法规和行政规章制度、文件规定。 +

+

+ 二、本单位(包含个人)保证不利用网络危害国家安全、泄露国家秘密,不侵犯国家的、社会的、集体的利益和第三方的合法权益,不从事违法犯罪活动。 +

+

+ 三、本单位(包含个人)承诺严格按照国家相关法律法规做好本单位(包含个人)网站的信息安全管理工作,按政府有关部门要求设立信息安全责任人和信息安全审查员,信息安全责任人和信息安全审查员应在通过公安机关的安全技术培训后,持证上岗。 +

+

+ 四、本单位(包含个人)承诺健全各项网络安全管理制度和落实各项安全保护技术措施。 +

+

+ 五、本单位(包含个人)承诺接受公安机关的监督和检查,如实主动提供有关安全保护的信息、资料及数据文件,积极协助查处通过国际联网的计算机信息网络违法犯罪行为。 +

+

+ 六、本单位(包含个人)承诺不通过互联网制作、复制、查阅和传播下列信息: +

+

+ 1、反对宪法所确定的基本原则的。 +
+ 2、危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的。 +
+ 3、损害国家荣誉和利益的。 +
+ 4、煽动民族仇恨、民族歧视,破坏民族团结的。 +
+ 5、破坏国家宗教政策,宣扬邪教和封建迷信的。 +
+ 6、散布谣言,扰乱社会秩序,破坏社会稳定的。 +
+ 7、散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的。 +
+ 8、侮辱或者诽谤他人,侵害他人合法权益的。 +
+ 9、含有法律法规禁止的其他内容的。 +

+

+ 七、本单位(包含个人)承诺不从事任何危害计算机信息网络安全的活动,包括但不限于: +

+

+ 1、未经允许,进入计算机信息网络或者使用计算机信息网络资源的。 +
+ 2、未经允许,对计算机信息网络功能进行删除、修改或者增加的。 +
+ 3、未经允许,对计算机信息网络中存储或者传输的数据和应用程序进行删除、修改或者增加的。 +
+ 4、故意制作、传播计算机病毒等破坏性程序的。 +
+ 5、其他危害计算机信息网络安全的。 +

+

+ 八、本单位(包含个人)承诺,当计算机信息系统发生重大安全事故时,立即采取应急措施,保留有关原始记录,并在24小时内向政府监管部门报告,并书面知会贵单位(包含个人)。 +

+

+ 九、本单位(包含个人)承诺,若本单位(包含个人)租用的拨号账号出现违规违法行为导致贵单位(指华连网络科技有限公司)被处罚的,造成的直接损失由本单位(包含个人)赔偿。 +

+

+ 十、若违反本承诺书有关条款和国家相关法律法规的,本单位(包含个人)直接承担相应法律责任,造成财产损失的,由本单位(包含个人)直接赔偿。同时,贵单位(指华连网络科技有限公司)有权暂停直至解除双方合作合同。 +

+

+ 十一、解除屏蔽后,qq和微信也仅可用于游戏之类的登录授权,不可用于聊天添加好友等聊天软件内部的功能,系统有严密日志,如有此类情况,账号立即封停,且不预退款,若触犯相关法律将上报给公安部门,请您知晓! +

+

十二、本承诺书自勾选确认之日起生效并遵行。

+
+
+ +
+ setAgreed(checked === true)} + /> + +
+ + + + +
+
+ ) +} diff --git a/src/components/composites/dialogs/editInfoDialog.tsx b/src/components/composites/dialogs/editInfoDialog.tsx index 7aaadd5..e737d25 100644 --- a/src/components/composites/dialogs/editInfoDialog.tsx +++ b/src/components/composites/dialogs/editInfoDialog.tsx @@ -79,7 +79,7 @@ export function EditInfoDialog({ return ( - + 编辑信息 diff --git a/src/components/composites/dialogs/realNameDialog.tsx b/src/components/composites/dialogs/realNameDialog.tsx new file mode 100644 index 0000000..971b4a4 --- /dev/null +++ b/src/components/composites/dialogs/realNameDialog.tsx @@ -0,0 +1,213 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { useState } from "react" +import { Controller, useForm } from "react-hook-form" +import { toast } from "sonner" +import { z } from "zod" +import { submitRealName } from "@/api/agreement" +import { QrCodeImage } from "@/components/qrCodeImage" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { FieldError } from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +const realNameSchema = z.object({ + name: z.string().trim().min(1, "请输入真实姓名"), + idCode: z + .string() + .trim() + .regex(/^\d{15}$|^\d{18}$|^\d{17}(\d|X|x)$/, "身份证号码不合法"), +}) + +type RealNameValues = z.infer + +export default function RealNameDialog({ + open, + onOpenChange, + onVerified, +}: { + open: boolean + onOpenChange: (open: boolean) => void + onVerified: () => void +}) { + const [stage, setStage] = useState<"form" | "scan">("form") + const [qrUrl, setQrUrl] = useState("") + const [submitting, setSubmitting] = useState(false) + + const form = useForm({ + resolver: zodResolver(realNameSchema), + defaultValues: { name: "", idCode: "" }, + }) + + const handleOpenChange = (next: boolean) => { + onOpenChange(next) + if (!next) { + form.reset({ name: "", idCode: "" }) + setStage("form") + setQrUrl("") + } + } + + const handleFinish = () => { + onVerified() + onOpenChange(false) + } + + const onSubmit = async (data: RealNameValues) => { + setSubmitting(true) + const res = await submitRealName({ + idCode: data.idCode, + name: data.name, + }) + setSubmitting(false) + + if (!res.success) { + toast.error(res.message) + return + } + + const { code, url } = res.data + if (code === 20000) { + toast.success("已经认证成功") + handleFinish() + return + } + if (code === 30000) { + toast.error("该身份已经进行认证,无法再次进行认证,请联系在线客服处理") + handleFinish() + return + } + if (code === 10000 && url) { + setQrUrl(url) + setStage("scan") + return + } + toast.error("请正确填写姓名和身份证号码") + } + + return ( + + + {stage === "form" ? ( + <> + + + 尊敬的用户,您还未进行实名认证! + + + +
+

+ 《中华人民共和国网络安全法》第二十四条规定: +
+ 网络运营者为用户办理网络接入、域名注册服务,办理固定电话、移动电话等入网手续,或者为用户提供信息发布、即时通讯等服务,在与用户签订协议或者确认提供服务时,应当要求用户提供真实身份信息。用户不提供真实身份信息的,网络运营者不得为其提供相关服务。 +

+

+ 1、为了你的帐号安全请勿使用他人手机号或身份信息 +

+

+ 2、聚IP承诺:用户的个人隐私数据仅与国家公安一所信息库比对,本公司不调用及储存用户的实人认证数据,仅接收公安一所方面的实人认证结果! +

+

3、若您扫脸认证不成功,请联系客服

+
+ +
+
+ + ( + <> + + {fieldState.error?.message} + + )} + /> +
+ +
+ + ( + <> + + {fieldState.error?.message} + + )} + /> +
+ + + + + +
+ + ) : ( + <> + + + 请打开微信扫码进行认证 + + + +

+ 若扫脸后仍未认证成功,请联系客服 +

+ +
+
+ +
+
+ + + + + + + )} +
+
+ ) +} diff --git a/src/components/qrCodeImage.tsx b/src/components/qrCodeImage.tsx new file mode 100644 index 0000000..1dff21d --- /dev/null +++ b/src/components/qrCodeImage.tsx @@ -0,0 +1,48 @@ +import QRCode from "qrcode" +import { useEffect, useState } from "react" +import { cn } from "@/lib/utils" + +export function QrCodeImage({ + text, + size = 70, + className, +}: { + text: string + size?: number + className?: string +}) { + const [dataUrl, setDataUrl] = useState("") + + useEffect(() => { + if (!text) { + setDataUrl("") + return + } + let cancelled = false + QRCode.toDataURL(text, { + width: size * 4, + margin: 1, + errorCorrectionLevel: "M", + }) + .then(url => { + if (!cancelled) setDataUrl(url) + }) + .catch(() => { + if (!cancelled) setDataUrl("") + }) + return () => { + cancelled = true + } + }, [text, size]) + + if (!dataUrl) return null + + return ( + 二维码 + ) +} diff --git a/src/home/header.tsx b/src/home/header.tsx index 027c001..3e64112 100644 --- a/src/home/header.tsx +++ b/src/home/header.tsx @@ -156,7 +156,7 @@ const MenuItem = ({ onMouseEnter: () => void onMouseLeave: () => void }) => { - const timeoutRef = useRef(null) + const timeoutRef = useRef | null>(null) const handleMouseEnter = () => { if (timeoutRef.current) { diff --git a/src/lib/models/dashboard.ts b/src/lib/models/dashboard.ts index eada7eb..72a4a50 100644 --- a/src/lib/models/dashboard.ts +++ b/src/lib/models/dashboard.ts @@ -11,17 +11,6 @@ export type AccountInfo = { taobao: string } -export type RecentOrderItem = { - date: string - product: string - plan: string - price: number - connections: number - account: string - orderAmount: number - paidAmount: number -} - export type ConsumptionStats = { today: number month: number diff --git a/vite.config.ts b/vite.config.ts index 07409ad..4bc826b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,6 +25,26 @@ export default defineConfig({ }, }, "/script": "https://php-api.juip.com", + "/http": { + target: "https://php-api.juip.com", + changeOrigin: true, + secure: false, + }, + "/agent": { + target: "https://php-api.juip.com", + changeOrigin: true, + secure: false, + }, + "/realname": { + target: "https://php-api.juip.com", + changeOrigin: true, + secure: false, + }, + "/tencent": { + target: "https://php-api.juip.com", + changeOrigin: true, + secure: false, + }, }, }, })