diff --git a/.env.development b/.env.development index 15515fd..1b2e05a 100644 --- a/.env.development +++ b/.env.development @@ -1,10 +1,12 @@ ### # @LastEditors: John # @Date: 2024-06-18 10:12:21 - # @LastEditTime: 2024-06-19 15:52:17 + # @LastEditTime: 2024-06-22 11:22:13 # @Author: John ### +VITE_BASE_URL=http://192.168.10.167:5173/ VITE_BASE_API_URL=/dev VITE_PARTICIPATE_CHAIN_ID=97 -VITE_NETWORK_USDT_ADDRESS=0x204074AD198E7c6e79E195DAf576bc7D53967B45 +VITE_PURCHASED_CONTRACT_ADDRESS=0x37644e2E1Ac2D0b87f693Ad64A154f3A7fe09b93 +VITE_NETWORK_USDT_ADDRESS=0xACFE3DF8ACeF83De51d53E22ADE30F18eaB4969A VITE_CHECK_TRANSACTION_DETAILS_URL=https://testnet.bscscan.com/ \ No newline at end of file diff --git a/.env.production b/.env.production index aef567a..b534bc1 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,4 @@ +VITE_BASE_URL=/ VITE_BASE_API_URL=/dev VITE_PARTICIPATE_CHAIN_ID=56 VITE_NETWORK_USDT_ADDRESS=0x55d398326f99059ff775485246999027b3197955 diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 0bc04b3..0d7d032 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/package.json b/package.json index deaebd6..08949f0 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@hyper-fetch/react": "^5.7.5", "@tanstack/react-query": "^5.45.1", "@web3modal/wagmi": "^5.0.2", + "ahooks": "^3.8.0", "antd-mobile": "^5.36.1", "clsx": "^2.1.1", "i18next": "^23.11.5", @@ -28,6 +29,7 @@ "vconsole": "^3.15.1", "viem": "^2.14.2", "wagmi": "^2.10.2", + "web3-utils": "^4.3.0", "zustand": "^4.5.2" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index 0efb6ee..74efe28 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,10 @@ /* * @LastEditors: John * @Date: 2024-06-17 17:20:03 - * @LastEditTime: 2024-06-19 16:59:21 + * @LastEditTime: 2024-06-21 14:19:52 * @Author: John */ -import { Route, Router, Routes } from "react-router-dom"; +import { Route, Routes } from "react-router-dom"; import "./style/ant-cover-m.css"; import "./style/react-data-table-component-cover-m.css"; import "./App.css"; @@ -19,14 +19,32 @@ import InvitationList from "./pages/InvitationList"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import useUserStore from "./store/User"; +import { getUrlQueryParam } from "./utils"; +import { UrlQueryParamsKey } from "./constants"; +import { signAndLogin } from "./utils/wallet"; +import { useAccount } from "wagmi"; function App() { const { i18n } = useTranslation(); - const { Lang: currantLang } = useUserStore(); + const { Lang: currantLang, UpdateInviteCode } = useUserStore(); + const { address } = useAccount(); useEffect(() => { i18n.changeLanguage(currantLang); + UpdateInviteCode(getUrlQueryParam(UrlQueryParamsKey.INVITE_CODE) || ""); return () => {}; }, []); + useEffect(() => { + return () => {}; + }, []); + + useEffect(() => { + (async () => { + await signAndLogin(address); + console.log("login success!"); + })(); + return () => {}; + }, [address]); + return ( <> diff --git a/src/assets/RMOB_logo.svg b/src/assets/RMOB_logo.svg new file mode 100644 index 0000000..af7adc7 --- /dev/null +++ b/src/assets/RMOB_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/RecordsItem.tsx b/src/components/RecordsItem.tsx index f66746a..c6c74c4 100644 --- a/src/components/RecordsItem.tsx +++ b/src/components/RecordsItem.tsx @@ -8,7 +8,7 @@ import classes from "./RecordsItem.module.css"; export default function RecordsItem({ itemList, }: { - itemList: { title: string; value: string; valueColor?: string }[]; + itemList: { title: string; value?: string; valueColor?: string }[]; }) { return ( <> diff --git a/src/constants/index.ts b/src/constants/index.ts index d46d0d7..4e003d0 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,13 +1,17 @@ /* * @LastEditors: John * @Date: 2024-06-17 17:57:13 - * @LastEditTime: 2024-06-19 16:26:15 + * @LastEditTime: 2024-06-21 11:45:40 * @Author: John */ export enum ASYNC_STORAGE_KEY { Store = "user.store", } +export enum UrlQueryParamsKey { + INVITE_CODE = "inviteCode", +} + export enum Lang { en = "en", cn = "cn", @@ -15,3 +19,8 @@ export enum Lang { jp = "jp", de = "de", } + +export enum CoinName { + USDT = "USDT", + RMOB = "RMOB", +} diff --git a/src/contract/abi/RedDevils.json b/src/contract/abi/RedDevils.json new file mode 100644 index 0000000..4a8d0c0 --- /dev/null +++ b/src/contract/abi/RedDevils.json @@ -0,0 +1,268 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + } + ], + "name": "buyHMNFT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "hongMoAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "payAddr", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + } + ], + "name": "BuySuccess", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenIn", + "type": "uint256" + } + ], + "name": "setTokenIndex", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + } + ], + "name": "upgradePrivilege", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyAddr", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + } + ], + "name": "UpgradeRange", + "type": "event" + }, + { + "inputs": [], + "name": "hongMoNFT", + "outputs": [ + { + "internalType": "contract HongMoNFT", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "price", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "usdc", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/contract/utils.ts b/src/contract/utils.ts index d3b291a..5628080 100644 --- a/src/contract/utils.ts +++ b/src/contract/utils.ts @@ -1,7 +1,7 @@ /* * @LastEditors: John * @Date: 2024-06-19 15:48:57 - * @LastEditTime: 2024-06-19 16:13:14 + * @LastEditTime: 2024-06-24 11:09:12 * @Author: John */ import { config } from "@/components/WalletProvider"; @@ -10,19 +10,26 @@ import { estimateGas, writeContract, waitForTransactionReceipt, + getAccount, } from "@wagmi/core"; import { encodeFunctionData } from "viem/utils"; import erc20Abi from "@/contract/abi/erc20abi.json"; import usdtAbi from "@/contract/abi/USDT.json"; +import RedDevilsAbi from "@/contract/abi/RedDevils.json"; import Toast from "antd-mobile/es/components/toast"; +import { EstimateGasErrorType, WriteContractErrorType } from "viem"; +import i18next from "i18next"; +import { BaseError } from "wagmi"; /** * @description 获取代币余额 * @param {string} fromAddress * @return {*} */ -export const getBalance = async (fromAddress: string): Promise => { +export const getBalance = async (): Promise => { return new Promise((reslove, reject) => { + const fromAddress = getAccount(config).address; + if (!fromAddress) return reject(new Error("address is emtiy")); readContract(config, { abi: erc20Abi, address: import.meta.env.VITE_NETWORK_USDT_ADDRESS, @@ -33,15 +40,12 @@ export const getBalance = async (fromAddress: string): Promise => { console.log("U余额:", res); if (typeof res == "undefined") { // 获取授权U失败 - Toast.show({ - icon: "fail", - content: "get balance of usdt error!", - }); + reject(new BaseError("get balance of usdt error!")); return; } reslove(res); }) - .catch((err) => { + .catch((err: BaseError) => { console.log("get balance of usdt err", err); reject(err); }); @@ -53,31 +57,28 @@ export const getBalance = async (fromAddress: string): Promise => { * @param {string} fromAddress * @return {*} */ -export const getApproveUsdt = async ( - contractAddress: string, - fromAddress: string -): Promise => { +export const getApproveUsdt = async (): Promise => { return new Promise((reslove, reject) => { + const fromAddress = getAccount(config).address; + if (!fromAddress) return reject(new Error("address is emtiy")); + readContract(config, { abi: erc20Abi, address: import.meta.env.VITE_NETWORK_USDT_ADDRESS, functionName: "allowance", - args: [fromAddress, contractAddress], + args: [fromAddress, import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS], }) .then((res: any) => { console.log("上次授权的U:", res); if (typeof res == "undefined") { // 获取授权U失败 - Toast.show({ - icon: "fail", - content: "get approve usdt error!", - }); + reject(new BaseError("get approve usdt error")); return; } reslove(res); }) - .catch((err) => { - console.log("get approve usdt err", err); + .catch((err: BaseError) => { + console.log("get approve usdt error", err); reject(err); }); }); @@ -88,15 +89,19 @@ export const getApproveUsdt = async ( * @param {bigint} uNum * @return {*} */ -export const authorizedU = async (uNum: bigint, contractAddress: string) => { - console.log("授权金额参数:", contractAddress, uNum); +export const authorizedU = async (uNum: bigint) => { + console.log( + "授权金额参数:", + import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, + uNum + ); return new Promise((reslove, reject) => { estimateGas(config, { to: import.meta.env.VITE_NETWORK_USDT_ADDRESS, data: encodeFunctionData({ abi: usdtAbi, functionName: "approve", - args: [contractAddress, uNum], + args: [import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, uNum], }), }) .then((gas) => { @@ -111,7 +116,7 @@ export const authorizedU = async (uNum: bigint, contractAddress: string) => { abi: usdtAbi, address: import.meta.env.VITE_NETWORK_USDT_ADDRESS, functionName: "approve", - args: [contractAddress, uNum], + args: [import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, uNum], gas: gasPrice, // gas, }) @@ -122,14 +127,154 @@ export const authorizedU = async (uNum: bigint, contractAddress: string) => { }); if (transactionReceipt.status == "success") reslove(); }) - .catch((err) => { + .catch((err: BaseError) => { console.log("approve error", err); reject(err); }); }) - .catch((err) => { + .catch((err: BaseError) => { console.log("estimate approve gas error", err); reject(err); }); }); }; + +/** + * payByContract + * @param amount + * @param orderID + * @returns + */ +export async function payByContract(amount: bigint, orderID: string) { + console.log("pay buy contract params", { amount, orderID }); + console.log("NETWORK_USDT:", import.meta.env.VITE_NETWORK_USDT_ADDRESS); + + return new Promise(async (reslove, reject) => { + try { + const balance = await getBalance(); + if (balance < amount) { + console.log("用户代币余额不足"); + reject(new BaseError(i18next.t("余额不足"))); + return; + } + + console.log("当前要授权的U:", amount); + let approvedU = await getApproveUsdt(); + if (approvedU < amount) { + await authorizedU(amount); + } + + console.log("参数:", amount, orderID); + estimateGas(config, { + to: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: RedDevilsAbi, + functionName: "buyHMNFT", + args: [amount, orderID], + }), + }) + .then((gas) => { + const gasPrice = (gas * 12n) / 10n; + console.log("estimate gas:%d , my gas: %d", gas, gasPrice); + writeContract(config, { + abi: RedDevilsAbi, + address: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, + functionName: "buyHMNFT", + args: [amount, orderID], + gas: gasPrice, + }) + .then((receipt) => { + console.log("write contract success!, receipt:", receipt); + reslove(receipt); + }) + .catch((err: BaseError) => { + console.log("buyHMNFT Transaction err", err.details); + reject(err); + }); + }) + .catch((err: BaseError) => { + console.log("buyHMNFT estimateGas err", err.details); + reject(err); + }); + } catch (err) { + console.log("pay By Contract catch err", err); + reject(new BaseError(`${err}`)); + } + }); +} + +/** + * upGradeByContract + * @param amount + * @param orderID + * @returns + */ +export async function upGradeByContract(amount: bigint, orderID: string) { + console.log("pay buy contract params", { amount, orderID }); + console.log("NETWORK_USDT:", import.meta.env.VITE_NETWORK_USDT_ADDRESS); + + return new Promise(async (reslove, reject) => { + try { + const balance = await getBalance(); + if (balance < amount) { + console.log("用户代币余额不足"); + reject(new BaseError(i18next.t("余额不足"))); + return; + } + + console.log("当前要授权的U:", amount); + let approvedU = await getApproveUsdt(); + if (approvedU < amount) { + await authorizedU(amount); + } + + console.log("参数:", amount, orderID); + estimateGas(config, { + to: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: RedDevilsAbi, + functionName: "upgradePrivilege", + args: [amount, orderID], + }), + }) + .then((gas) => { + const gasPrice = (gas * 12n) / 10n; + console.log("estimate gas:%d , my gas: %d", gas, gasPrice); + writeContract(config, { + abi: RedDevilsAbi, + address: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, + functionName: "upgradePrivilege", + args: [amount, orderID], + gas: gasPrice, + }) + .then((receipt) => { + console.log("write contract success!, receipt:", receipt); + reslove(receipt); + }) + .catch((err: BaseError) => { + console.log("upgradePrivilege Transaction err", err.details); + reject(err); + }); + }) + .catch((err: BaseError) => { + console.log("upgradePrivilege estimateGas err", err.details); + reject(err); + }); + } catch (err) { + reject(new BaseError(`${err}`)); + } + }); +} + +/** + * receiveByContract + * @param amount + * @param orderID + * @returns + */ +export async function receiveByContract(amount: bigint, orderID: string) { + console.log("pay buy contract params", { amount, orderID }); + console.log("NETWORK_USDT:", import.meta.env.VITE_NETWORK_USDT_ADDRESS); + + return new Promise(async (reslove, reject) => {}); +} diff --git a/src/hook/usePollingCheckBuyStatus.ts b/src/hook/usePollingCheckBuyStatus.ts new file mode 100644 index 0000000..e97ff25 --- /dev/null +++ b/src/hook/usePollingCheckBuyStatus.ts @@ -0,0 +1,83 @@ +/* + * @LastEditors: John + * @Date: 2024-06-21 16:08:23 + * @LastEditTime: 2024-06-21 16:12:31 + * @Author: John + */ +import { useRef, useState } from "react"; +import { waitForTransactionReceipt } from "@wagmi/core"; +import { config } from "@/components/WalletProvider"; +/** + * @description: 轮询查询交易,获取nft + * @return {*} + */ +export default function (type: "NFT" | "NORMAL") { + const [buyNftIds, setBuyNftIds] = useState(""); + const [transcationStatus, setTranscationStatus] = useState< + "success" | undefined + >(undefined); + const stop = useRef(false); + + function startPollingCheckBuyStatus(hash: string) { + setTranscationStatus(undefined); + setBuyNftIds(""); + polling(hash); + } + + const checkStatus = async (hash: string) => { + return new Promise(async (reslove) => { + // let res = await API_GET_ORDER_STATE_BY_HASH(hash); + // setBuyNftIds(res.nftIds); + // console.log("得到nft ids:", res.nftIds); + + const transactionReceipt = await waitForTransactionReceipt(config, { + hash: hash as `0x${string}`, + }); + + console.log("transaction receipt:", transactionReceipt); + if (transactionReceipt.status == "success") { + if (type == "NFT") { + console.log("transaction receipt success:", transactionReceipt); + const nftLogs = transactionReceipt.logs.filter( + (v) => + v.topics.length === 4 && + v.topics[1] === + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); // 过滤(挖币的日志) + const nftDataArr = nftLogs.map((v) => v.topics[3]); + const nftDataStr = nftDataArr.map( + (v) => `#${parseInt(v as string, 16)}` + ); + setBuyNftIds(nftDataStr.join(",")); + } else { + setTranscationStatus("success"); + stopPollingCheckBuyStatus(); + reslove(); + return; + } + } + + setTimeout(() => { + reslove(); + }, 2000); + }); + }; + + const polling = async (hash: string) => { + await checkStatus(hash); + if (stop.current) return; + polling(hash); + }; + + function stopPollingCheckBuyStatus() { + stop.current = true; + } + + return { + transcationStatus, + setBuyNftIds, + buyNftIds, + startPollingCheckBuyStatus, + stopPollingCheckBuyStatus, + }; +} diff --git a/src/i18n/translation/cn.json b/src/i18n/translation/cn.json index a5db48a..be27211 100644 --- a/src/i18n/translation/cn.json +++ b/src/i18n/translation/cn.json @@ -35,17 +35,17 @@ "MINT余量:": "MINT余量:", "当前MINT价格:": "当前MINT价格:", "价格说明:": "价格说明:", - "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。": "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。", + "{{value1}} USDT起,每增加 {{value2}} 名普通会员,NFT价格上涨 {{value3}},既:前 {{value2}} 名价格为 {{value1}} USDT/枚,value4 名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。": "{{value1}} USDT起,每增加{{value2}}名普通会员,NFT价格上涨{{value3}},既:前{{value2}}名价格为{{value1}} USDT/枚,{{value4}}名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。", "授权USDT": "授权USDT", "当前级别": "当前级别", "普通活跃": "普通活跃", "提升级别": "提升级别", "社长": "社长", "当前升级价格:": "当前升级价格:", - "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。": "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。", + "升级费用xxx USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加xxx,既第二位社长升级铸造费用为xxxu+xxx*xxx=xxx USDT,以此类推。": "升级费用{{value1}} USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加{{value2}},既第二位社长升级铸造费用为{{value1}}u+{{value1}}*{{value2}}={{value3}} USDT,以此类推。", "升级条件": "升级条件", "普通会员": "普通会员", - "(限量20000个)": "(限量20000个)", + "(限量xxx个)": "(限量 {{value}} 个)", "1. MINT一枚NFT成为非活跃普通会员": "1. MINT一枚NFT成为非活跃普通会员", "2. MINT NFT并推荐MINT成为活跃普通会员": "2. MINT NFT并推荐MINT成为活跃普通会员", "3. 享1%代币空投平分(所有普通会员)": "3. 享1%代币空投平分(所有普通会员)", @@ -87,5 +87,19 @@ "活跃普通": "活跃普通", "复制成功": "复制成功", - "领取成功,前往钱包查看": "领取成功,前往钱包查看" + "领取成功,前往钱包查看": "领取成功,前往钱包查看", + "链接钱包中...": "链接钱包中...", + "无": "无", + "服务器错误": "服务器错误", + "余额不足": "余额不足", + "MINT成功,返回首页查看": "MINT成功,返回首页查看", + "正在授权USDT": "正在授权USDT", + "购买中": "购买中", + "领取中": "领取中", + "MINT Nft 获取邀请链接": "MINT Nft 获取邀请链接", + "链接钱包获取邀请链接": "链接钱包获取邀请链接", + "正在获取已授权金额": "正在获取已授权金额", + "升级成功,返回首页查看": "升级成功,返回首页查看", + "无级别提升": "无级别提升", + "没有更多数据了": "没有更多数据了" } diff --git a/src/i18n/translation/de.json b/src/i18n/translation/de.json index b4d9452..4a43269 100644 --- a/src/i18n/translation/de.json +++ b/src/i18n/translation/de.json @@ -35,17 +35,17 @@ "MINT余量:": "Verbleibende MINT:", "当前MINT价格:": "Aktueller MINT-Preis:", "价格说明:": "Preiserklärung:", - "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。": "Beginnend bei 100USDT steigt der NFT-Preis um 1% für jede zusätzlichen 100 Mitglieder: die ersten 100 kosten 100USDT pro Stück, 101-200 kosten 101USDT pro Stück usw.", + "{{value1}} USDT起,每增加 {{value2}} 名普通会员,NFT价格上涨 {{value3}},既:前 {{value2}} 名价格为 {{value1}} USDT/枚,value4 名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。": "Beginnend bei {{value1}} USDT steigt der NFT-Preis um {{value3}} für jede zusätzlichen {{value2}} Mitglieder: die ersten {{value2}} kosten {{value1}} USDT pro Stück, {{value4}} kosten {{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT pro Stück usw.", "授权USDT": "USDT autorisieren", "当前级别": "Aktuelles Level", "普通活跃": "Aktives Mitglied", "提升级别": "Levelaufstieg", "社长": "Vorsitzender", "当前升级价格:": "Aktueller Upgrade-Preis:", - "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。": "Upgrade-Kosten ab 100USDT, davon 2USDT Gas-Gebühren, 50% des Restbetrags gehen in den Liquiditätspool und die anderen 50% werden auf alle erfolgreichen Vorsitzenden und Fundmanager aufgeteilt. Ab dem zweiten Vorsitzenden steigen die Prägekosten um 10% pro Aufstieg, also 110USDT für den zweiten, usw.", + "升级费用xxx USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加xxx,既第二位社长升级铸造费用为xxxu+xxx*xxx=xxx USDT,以此类推。": "Upgrade-Kosten ab {{value1}} USDT, davon 2USDT Gas-Gebühren, 50% des Restbetrags gehen in den Liquiditätspool und die anderen 50% werden auf alle erfolgreichen Vorsitzenden und Fundmanager aufgeteilt. Ab dem zweiten Vorsitzenden steigen die Prägekosten um {{value2}} pro Aufstieg, also {{value1}}u+{{value1}}*{{value2}}={{value3}} USDT für den zweiten, usw.", "升级条件": "Upgrade-Bedingungen", "普通会员": "Normales Mitglied", - "(限量20000个)": "(Begrenzt auf 20.000 Stück)", + "(限量xxx个)": "(Begrenzt auf {{value}} Stück)", "1. MINT一枚NFT成为非活跃普通会员": "1. Präge ein NFT, um inaktives Mitglied zu werden", "2. MINT NFT并推荐MINT成为活跃普通会员": "2. Präge und empfehle NFTs, um aktives Mitglied zu werden", "3. 享1%代币空投平分(所有普通会员)": "3. Erhalte 1% Token-Airdrop gleichmäßig verteilt (alle normalen Mitglieder)", @@ -87,5 +87,19 @@ "活跃普通": "Aktiv allgemein", "复制成功": "Erfolgreich kopiert", - "领取成功,前往钱包查看": "Erfolgreich abgerufen. Bitte überprüfen Sie Ihre Brieftasche." + "领取成功,前往钱包查看": "Erfolgreich abgerufen. Bitte überprüfen Sie Ihre Brieftasche.", + "链接钱包中...": "Wallet wird verbunden...", + "无": "Keiner", + "服务器错误": "Serverfehler", + "余额不足": "Unzureichendes Guthaben", + "MINT成功,返回首页查看": "MINT erfolgreich, zur Startseite zurückkehren, um nachzusehen.", + "正在授权USDT": "USDT wird autorisiert", + "购买中": "In Bearbeitung", + "领取中": "In Bearbeitung", + "MINT Nft 获取邀请链接": "MINT Nft Einladungslink erhalten", + "链接钱包获取邀请链接": "Verknüpfen Sie Ihre Brieftasche, um den Einladungslink zu erhalten.", + "正在获取已授权金额": "Aktuell wird der autorisierte Betrag abgerufen.", + "升级成功,返回首页查看": "Upgrade erfolgreich abgeschlossen, zurück zur Startseite zur Ansicht.", + "无级别提升": "Beförderung ohne Rang", + "没有更多数据了": "Keine weiteren Daten" } diff --git a/src/i18n/translation/en.json b/src/i18n/translation/en.json index 4b734eb..bef1d99 100644 --- a/src/i18n/translation/en.json +++ b/src/i18n/translation/en.json @@ -12,7 +12,7 @@ "取消": "Cancel", "普通非活跃": "Inactive Member", "升级": "Upgrade", - "链接钱包": "Link Wallet", + "链接钱包": "Connect Wallet", "邀请铸造": "Invite to Mint", "团队社长": "Team Leader", "邀请空投": "Invite Airdrop", @@ -35,17 +35,17 @@ "MINT余量:": "Remaining MINT:", "当前MINT价格:": "Current MINT Price:", "价格说明:": "Price Explanation:", - "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。": "Starting at 100USDT, the NFT price increases by 1% for every 100 new members: first 100 at 100USDT each, 101-200 at 101USDT each, and so on.", + "{{value1}} USDT起,每增加 {{value2}} 名普通会员,NFT价格上涨 {{value3}},既:前 {{value2}} 名价格为 {{value1}} USDT/枚,value4 名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。": "Starting at {{value1}} USDT, the NFT price increases by {{value3}} for every {{value2}} new members: first {{value2}} at {{value1}} USDT each, {{value4}} at {{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT each, and so on.", "授权USDT": "Authorize USDT", "当前级别": "Current Level", "普通活跃": "Active Member", "提升级别": "Upgrade Level", "社长": "Leader", "当前升级价格:": "Current Upgrade Price:", - "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。": "Upgrade cost starts at 100USDT, including 2USDT gas fee; 50% goes to the fund pool, and 50% is distributed among successful Leaders and Foundation Leaders. From the second Leader onwards, each upgrade cost increases by 10%, i.e., second Leader upgrade costs 110USDT, and so on.", + "升级费用xxx USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加xxx,既第二位社长升级铸造费用为xxxu+xxx*xxx=xxx USDT,以此类推。": "Upgrade cost starts at {{value1}} USDT, including 2USDT gas fee; 50% goes to the fund pool, and 50% is distributed among successful Leaders and Foundation Leaders. From the second Leader onwards, each upgrade cost increases by {{value2}}, i.e., second Leader upgrade costs {{value1}}u+{{value1}}*{{value2}}={{value3}} USDT, and so on.", "升级条件": "Upgrade Conditions", "普通会员": "Regular Member", - "(限量20000个)": "(Limited to 20000)", + "(限量xxx个)": "(Limited to {{value}})", "1. MINT一枚NFT成为非活跃普通会员": "1. MINT an NFT to become an Inactive Member", "2. MINT NFT并推荐MINT成为活跃普通会员": "2. MINT NFT and recommend MINT to become an Active Member", "3. 享1%代币空投平分(所有普通会员)": "3. 1% token airdrop shared among all members", @@ -87,5 +87,19 @@ "活跃普通": "Active Member", "复制成功": "Copy successful", - "领取成功,前往钱包查看": "Claim successful, please check your wallet." + "领取成功,前往钱包查看": "Claim successful, please check your wallet.", + "链接钱包中...": "connecting wallet...", + "无": "None", + "服务器错误": "Server Error", + "余额不足": "Insufficient balance", + "MINT成功,返回首页查看": "MINT succeeded, return to the homepage to check.", + "正在授权USDT": "Authorizing USDT", + "购买中": "Purchasing", + "领取中": "Processing", + "MINT Nft 获取邀请链接": "MINT Nft invite link", + "链接钱包获取邀请链接": "Link your wallet to get the invitation link.", + "正在获取已授权金额": "Currently retrieving authorized amount.", + "升级成功,返回首页查看": "Upgrade successful, return to homepage to view.", + "无级别提升": "Promotion without rank", + "没有更多数据了": "No more data" } diff --git a/src/i18n/translation/jp.json b/src/i18n/translation/jp.json index 30c72a2..7b65ce9 100644 --- a/src/i18n/translation/jp.json +++ b/src/i18n/translation/jp.json @@ -35,17 +35,17 @@ "MINT余量:": "残りのMINT:", "当前MINT价格:": "現在のMINT価格:", "价格说明:": "価格説明:", - "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。": "100USDTから、100人の会員が増えるごとにNFT価格が1%上昇:最初の100人は100USDT/個、101-200人は101USDT/個、以降も同様。", + "{{value1}} USDT起,每增加 {{value2}} 名普通会员,NFT价格上涨 {{value3}},既:前 {{value2}} 名价格为 {{value1}} USDT/枚,value4 名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。": "{{value1}} USDTから、{{value2}}人の会員が増えるごとにNFT価格が{{value3}}上昇:最初の{{value2}}人は{{value1}}USDT/個、{{value4}}人は{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/個、以降も同様。", "授权USDT": "USDTの承認", "当前级别": "現在のレベル", "普通活跃": "活躍メンバー", "提升级别": "レベルアップ", "社长": "リーダー", "当前升级价格:": "現在のアップグレード価格:", - "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。": "アップグレード費用は100USDTから、うちガス代2USDT、残りの50%は資金プールに入り、もう50%はアップグレードに成功したリーダーとファンドリーダーに分配。2人目のリーダーからは、アップグレード費用が10%ずつ増加、つまり2人目は110USDT、以降も同様。", + "升级费用xxx USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加xxx,既第二位社长升级铸造费用为xxxu+xxx*xxx=xxx USDT,以此类推。": "アップグレード費用は{{value1}} USDTから、うちガス代2USDT、残りの50%は資金プールに入り、もう50%はアップグレードに成功したリーダーとファンドリーダーに分配。2人目のリーダーからは、アップグレード費用が{{value2}}ずつ増加、つまり2人目は{{value1}}u+{{value1}}*{{value2}}={{value3}} USDT、以降も同様。", "升级条件": "アップグレード条件", "普通会员": "一般会員", - "(限量20000个)": "(限定20000個)", + "(限量xxx个)": "(限定 {{value}} 個)", "1. MINT一枚NFT成为非活跃普通会员": "1. NFTを1枚鋳造して非活躍メンバーになる", "2. MINT NFT并推荐MINT成为活跃普通会员": "2. NFTを鋳造し、他者に鋳造を推奨して活躍メンバーになる", "3. 享1%代币空投平分(所有普通会员)": "3. 一般会員は1%のトークンエアドロップを均等に分配", @@ -87,5 +87,19 @@ "活跃普通": "活躍一般", "复制成功": "コピーが成功しました", - "领取成功,前往钱包查看": "受け取りが成功しました。ウォレットで確認してください。" + "领取成功,前往钱包查看": "受け取りが成功しました。ウォレットで確認してください。", + "链接钱包中...": "ウォレットをリンク中...", + "无": "無", + "服务器错误": "サーバーエラー", + "余额不足": "ざんだかふそく", + "MINT成功,返回首页查看": "MINTが成功しました。ホームページに戻って確認してください", + "正在授权USDT": "USDTの認証中", + "购买中": "購入中 (こうにゅうちゅう)", + "领取中": "しょりちゅう", + "MINT Nft 获取邀请链接": "MINT Nft 招待リンクを取得する", + "链接钱包获取邀请链接": "ウォレットをリンクして招待リンクを取得します。", + "正在获取已授权金额": "承認済み金額を取得中です", + "升级成功,返回首页查看": "アップグレードが成功しました。ホームページに戻って確認してください。", + "无级别提升": "階級なしの昇進", + "没有更多数据了": "これ以上のデータはありません" } diff --git a/src/i18n/translation/tw.json b/src/i18n/translation/tw.json index c1f5011..7296d86 100644 --- a/src/i18n/translation/tw.json +++ b/src/i18n/translation/tw.json @@ -35,17 +35,17 @@ "MINT余量:": "MINT餘量:", "当前MINT价格:": "當前MINT價格:", "价格说明:": "價格說明:", - "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。": "100USDT起,每增加100名普通會員,NFT價格上漲1%,即:前100名價格為100USDT/枚,101-200名價格為100u+100u*1%=101USDT/枚,以此類推。", + "{{value1}} USDT起,每增加 {{value2}} 名普通会员,NFT价格上涨 {{value3}},既:前 {{value2}} 名价格为 {{value1}} USDT/枚,value4 名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。": "{{value1}} USDT起,每增加{{value2}}名普通會員,NFT價格上漲{{value3}},即:前{{value2}}名價格為{{value1}} USDT/枚,{{value4}}名價格為{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此類推。", "授权USDT": "授權USDT", "当前级别": "當前級別", "普通活跃": "普通活躍", "提升级别": "提升級別", "社长": "社長", "当前升级价格:": "當前升級價格:", - "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。": "升級費用100USDT起,其中gas費2USDT,剩餘部分50%進入資金池,另外50%平均分給所有升級成功的社長和基金會社長。自第二個社長升級開始,每升級一名社長所需鑄造費用增加10%,即第二位社長升級鑄造費用為100u+100*10%=110USDT,以此類推。", + "升级费用xxx USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加xxx,既第二位社长升级铸造费用为xxxu+xxx*xxx=xxx USDT,以此类推。": "升級費用{{value1}} USDT起,其中gas費2USDT,剩餘部分50%進入資金池,另外50%平均分給所有升級成功的社長和基金會社長。自第二個社長升級開始,每升級一名社長所需鑄造費用增加{{value2}},即第二位社長升級鑄造費用為{{value1}}u+{{value1}}*{{value2}}={{value3}} USDT,以此類推。", "升级条件": "升級條件", "普通会员": "普通會員", - "(限量20000个)": "(限量20000個)", + "(限量xxx个)": "(限量 {{value}} 個)", "1. MINT一枚NFT成为非活跃普通会员": "1. MINT一枚NFT成為非活躍普通會員", "2. MINT NFT并推荐MINT成为活跃普通会员": "2. MINT NFT並推薦MINT成為活躍普通會員", "3. 享1%代币空投平分(所有普通会员)": "3. 享1%代幣空投平分(所有普通會員)", @@ -87,5 +87,19 @@ "活跃普通": "活躍普通", "复制成功": "複製成功", - "领取成功,前往钱包查看": "領取成功,前往錢包查看" + "领取成功,前往钱包查看": "領取成功,前往錢包查看", + "链接钱包中...": "連結錢包中...", + "无": "無", + "服务器错误": "伺服器錯誤", + "余额不足": "餘額不足", + "MINT成功,返回首页查看": "MINT成功,返回首頁查看", + "正在授权USDT": "正在授權USDT", + "购买中": "購買中", + "领取中": "處理中", + "MINT Nft 获取邀请链接": "MINT Nft 獲取邀請鏈接", + "链接钱包获取邀请链接": "連結錢包以獲取邀請鏈接。", + "正在获取已授权金额": "正在取得已授權金額", + "升级成功,返回首页查看": "升級成功,返回首頁查看。", + "无级别提升": "無級別提升", + "没有更多数据了": "沒有更多數據了" } diff --git a/src/main.tsx b/src/main.tsx index dc791d6..c085e6b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ /* * @LastEditors: John * @Date: 2024-06-17 17:20:03 - * @LastEditTime: 2024-06-19 18:07:46 + * @LastEditTime: 2024-06-20 11:43:14 * @Author: John */ import "@/i18n/init.ts"; @@ -15,8 +15,9 @@ import EventBusProvider from "./context/EventBusContext.tsx"; import { WalletProvider } from "./components/WalletProvider.tsx"; import flexible from "./utils/flexible.ts"; import VConsole from "vconsole"; +import { getUrlQueryParam } from "./utils/index.ts"; -if (import.meta.env.DEV) { +if (getUrlQueryParam("vconsole") === "1") { new VConsole(); } flexible(window, document); diff --git a/src/pages/AssetRecord.tsx b/src/pages/AssetRecord.tsx index 71328e8..3fdf1ee 100644 --- a/src/pages/AssetRecord.tsx +++ b/src/pages/AssetRecord.tsx @@ -1,105 +1,209 @@ /* * @LastEditors: John * @Date: 2024-06-18 17:57:13 - * @LastEditTime: 2024-06-19 14:19:55 + * @LastEditTime: 2024-06-24 10:44:10 * @Author: John */ import Tabs from "antd-mobile/es/components/tabs"; import classes from "./AssetRecord.module.css"; -import { cn } from "@/utils"; +import { cn, getUrlQueryParam } from "@/utils"; import CapsuleTabs from "antd-mobile/es/components/capsule-tabs"; import RecordsItem from "@/components/RecordsItem"; import { useTranslation } from "react-i18next"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { api_pagling_query_income_record } from "@/server/api"; +import { IncomeRecord, IncomeRecordType } from "@/server/module"; +import { Empty, InfiniteScroll } from "antd-mobile"; +import { CoinName } from "@/constants"; export default function () { const { t } = useTranslation(); + const coinId = useMemo(() => getUrlQueryParam("id"), []); + const coinName = useMemo(() => getUrlQueryParam("name"), []); + const currentType = useRef<1 | 2>(1); + const [issueRecords, setIssueRecords] = useState([]); + const [receiveRecord, setReceiveRecord] = useState( + [] + ); + const conditions = useRef(); + + const pageNum = useRef(0); + const hasMore = useRef(true); + useEffect(() => { + return () => {}; + }, [coinId]); + + async function getRecord() { + return new Promise(async (reslove) => { + if (!coinId) return; + + if (!hasMore.current) return; + + const pageSize = 20; + pageNum.current++; + const { data } = await api_pagling_query_income_record().send({ + queryParams: { + id: coinId, + type: currentType.current, + pageNum: pageNum.current, + pageSize, + ...(conditions.current && currentType.current == 1 + ? { status: conditions.current } + : {}), + }, + }); + + if (!data?.data.records) return; + + if (data.data.records.length < pageSize) hasMore.current = false; + + if (currentType.current == 1) { + setIssueRecords([...issueRecords, ...data?.data.records]); + } else { + setReceiveRecord([...receiveRecord, ...data?.data.records]); + } + reslove(); + }); + } + + function resetPaging() { + if (currentType.current == 1) { + setIssueRecords([]); + } else if (currentType.current == 2) { + setReceiveRecord([]); + } + pageNum.current = 0; + hasMore.current = true; + } + return ( <> - + { + if (parseInt(key) == 1) { + currentType.current = 1; + resetPaging(); + } else { + currentType.current = 2; + resetPaging(); + } + }} + > - - - - 20NFT")} key="3" /> - -
    -
  • - -
  • + {coinName == CoinName.USDT && ( + { + switch (key) { + case "1": + conditions.current = undefined; + break; + case "2": + conditions.current = 5; + break; + case "3": + conditions.current = 4; + break; -
  • - 20NFT"), - }, - { title: t("发放时间"), value: "2024-06-01 12:23:45" }, - { title: t("发放数量"), value: "2.00 USDT" }, - ]} - /> -
  • + default: + break; + } + resetPaging(); + }} + > + + + 20NFT")} key="3" /> +
    + )} + + {coinName == CoinName.RMOB && ( + { + switch (key) { + case "1": + conditions.current = undefined; + break; + case "2": + conditions.current = 6; + break; + case "3": + conditions.current = 7; + break; + case "4": + conditions.current = 8; + break; + case "5": + conditions.current = 9; + break; + default: + break; + } + resetPaging(); + }} + > + + + + + + + )} +
      + {issueRecords?.map((v, i) => ( +
    • + +
    • + ))} + {issueRecords?.length == 0 && } + + {t("没有更多数据了")} +
      -
    • - -
    • -
    • - -
    • -
    • - -
    • + {receiveRecord?.map((v, i) => ( +
    • + +
    • + ))} + {receiveRecord?.length == 0 && } + + {t("没有更多数据了")} +
    diff --git a/src/pages/Home.module.css b/src/pages/Home.module.css index cdf6520..1d9dc48 100644 --- a/src/pages/Home.module.css +++ b/src/pages/Home.module.css @@ -235,7 +235,7 @@ margin-top: 10px; width: 345px; - min-height: 236px; + /* min-height: 236px; */ border-radius: 16px; opacity: 1; @@ -326,6 +326,8 @@ .nftToken_content_token_top { padding: 14px 15px; border-bottom: 1px solid #404040; + display: flex; + flex-direction: column; span { opacity: 1; @@ -468,7 +470,7 @@ z-index: 1; box-sizing: border-box; - > span { + span { /* 自动布局子元素 */ opacity: 1; @@ -499,6 +501,7 @@ justify-content: center; align-items: center; gap: 16px; + padding: 0 43px; .nftToken_content_nft_mint_btn { /* 自动布局子元素 */ width: 169px; @@ -643,7 +646,7 @@ box-sizing: border-box; margin-top: 10px; - span { + > span { &:nth-of-type(1) { /* 自动布局子元素 */ opacity: 0.8; @@ -673,21 +676,18 @@ font-variation-settings: "opsz" auto; font-feature-settings: "kern" on; - color: #ffffff; + color: #eaeaea; - z-index: 0; - - display: flex; - align-items: center; - gap: 16px; - - .invite_content_icon { - width: 24px; - height: 24px; - } + z-index: 1; } + } + .invite_content_link { + display: flex; + align-items: center; + justify-content: space-between; - &:nth-of-type(3) { + > span { + width: 265px; /* 自动布局子元素 */ opacity: 1; @@ -699,9 +699,20 @@ font-variation-settings: "opsz" auto; font-feature-settings: "kern" on; - color: #eaeaea; + color: #ffffff; - z-index: 1; + z-index: 0; + + display: flex; + align-items: center; + gap: 16px; + + white-space: nowrap; + + .invite_content_icon { + width: 24px; + height: 24px; + } } } } diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4447855..1300004 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -2,17 +2,29 @@ import classes from "./Home.module.css"; import useUserStore from "@/store/User"; import { cn, copyText, shortenString } from "@/utils"; import { useWeb3Modal } from "@web3modal/wagmi/react"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import logo from "@/assets/logo.svg"; import nftBg from "@/assets/nft_bg.svg"; import usdtBg from "@/assets/usdt_bg.svg"; +import RMOB_logo from "@/assets/RMOB_logo.svg"; import IconFont from "@/components/iconfont"; -import { useAccount } from "wagmi"; +import { BaseError, useAccount } from "wagmi"; import { disconnect } from "wagmi/actions"; import { config } from "@/components/WalletProvider"; import { useNavigate } from "react-router-dom"; -import { Toast } from "antd-mobile"; +import { Button, Dialog, Ellipsis, Empty, Toast } from "antd-mobile"; +import { loginOut, signAndLogin } from "@/utils/wallet"; +import { + api_claim_income, + api_get_homepage_user_data, + api_users_cancel_orders, +} from "@/server/api"; +import { UserHomeData } from "@/server/module"; +import { UrlQueryParamsKey } from "@/constants"; +import { receiveByContract } from "@/contract/utils"; +import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus"; +import { ToastHandler } from "antd-mobile/es/components/toast"; export default function () { const { Token, UpdateToken } = useUserStore(); @@ -22,10 +34,40 @@ export default function () { const [tabIndex, setTabIndex] = useState(0); const navigate = useNavigate(); + const [userData, setUserData] = useState(); + + const userInviteLink = useMemo( + () => + `${import.meta.env.VITE_BASE_URL}?${UrlQueryParamsKey.INVITE_CODE}=${ + userData?.invitationCode || "" + }`, + [userData] + ); + const receiveLoadingToast = useRef(); + const { transcationStatus, startPollingCheckBuyStatus } = + usePollingCheckBuyStatus("NORMAL"); + useEffect(() => { - UpdateToken("user token abc"); + getHomeData(); return () => {}; - }, []); + }, [Token]); + + useEffect(() => { + if (transcationStatus == "success") { + receiveLoadingToast.current?.close(); + Dialog.alert({ + content: `${t("领取成功,前往钱包查看")}`, + confirmText: "OK", + }); + } + + return () => {}; + }, [transcationStatus]); + + async function getHomeData() { + const { data } = await api_get_homepage_user_data().send({}); + setUserData(data?.data); + } useEffect(() => { console.log("user token:", Token); @@ -47,6 +89,7 @@ export default function () { { disconnect(config); + loginOut(); }} name="tuichu" className={classes.userinfo_top_right_wallet_disconnect} @@ -54,26 +97,62 @@ export default function () { />
    -
    - - {t("普通非活跃")} -
    -
    { - navigate("/levelup"); - }} - > - {t("升级")} - -
    + {userData && ( + <> +
    + {userData.level == 0 && ( + <> + + {t("普通非活跃")} + + )} + {userData.level == 1 && ( + <> + + {t("普通活跃")} + + )} + {userData.level == 2 && ( + <> + + {t("社长")} + + )} + {userData.level == 3 && ( + <> + + {t("基金会社长")} + + )} +
    + +
    { + navigate("/levelup"); + }} + > + {t("升级")} + +
    + + )}
    ) : ( @@ -91,15 +170,21 @@ export default function () {
    • - 20 + + {userData?.mintNumber || 0} + {t("邀请铸造")}
    • - 20 + + {userData?.presidentNumber || 0} + {t("团队社长")}
    • - 3 + + {userData?.airdropNumber || 0} + {t("邀请空投")}
    @@ -126,10 +211,10 @@ export default function () { <> {address ? ( <> - {true ? ( + {userData?.nftId ? (
    - # 737389 + # ${userData?.nftId} { navigate("/mint"); @@ -188,22 +273,49 @@ export default function () {
      - { - navigate("/assetrecord"); - }} - /> - { - navigate("/airdroprecord"); - }} - /> + {userData?.userIncomes.map((v, i) => ( + { + navigate(`/assetrecord?id=${v.id}&name=${v.coinName}`); + }} + onReceive={async () => { + receiveLoadingToast.current = Toast.show({ + icon: "loading", + duration: 0, + content: t("领取中"), + maskClickable: false, + }); + const { data } = await api_claim_income().send({ + queryParams: { id: v.id }, + }); + const orderInfo = data?.data; + if (!orderInfo?.orderNumber) return; + const buyAmount = BigInt( + orderInfo?.claimQuantity || "" + ); + receiveByContract(buyAmount, orderInfo?.orderNumber) + .then((hash) => { + console.log("领取成功!hash:", hash); + getHomeData(); + startPollingCheckBuyStatus(hash); + }) + .catch(async (err: BaseError) => { + receiveLoadingToast.current?.close(); + Toast.show({ + content: err.shortMessage, + icon: "fail", + }); + }); + }} + /> + ))} + + {(userData?.userIncomes.length == 0 || + !userData?.userIncomes) && }
    )} @@ -213,32 +325,50 @@ export default function () {
    {t("邀请")} - { - navigate("/invitationlist"); - }} - > - {t("邀请列表")}{" "} - - + {address && ( + { + navigate("/invitationlist"); + }} + > + {t("邀请列表")}{" "} + + + )}
    {t("邀请链接")} - - https://www.rmob_nft.com/regster=id?1{" "} - { - copyText("https://www.rmob_nft.com/regster=id?1"); - }} - className={classes.invite_content_icon} - name="fuzhi" - color={"#fff"} - />{" "} - +
    + {address ? ( + <> + {userData?.nftId ? ( + <> + {shortenString(userInviteLink, 15, 15)} + { + copyText(userInviteLink); + }} + className={classes.invite_content_icon} + name="fuzhi" + color={"#fff"} + />{" "} + + ) : ( + <> + {t("MINT Nft 获取邀请链接")} + + )} + + ) : ( + <> + {t("链接钱包获取邀请链接")} + + )} +
    {t( @@ -253,15 +383,15 @@ export default function () {
    • {t("资金池")} - 10000 + {userData?.pools || 0}
    • {t("社长席位")} - 499 + {userData?.president || 0}
    • {t("基金会社长席位")} - 19 + {userData?.foundation || 0}
    @@ -275,16 +405,19 @@ function ReceiveCom({ tokenNum, toReceive, onAssetRec, + onReceive, }: { tokenName: string; - tokenNum: number; + tokenNum: string; toReceive: number; onAssetRec: () => void; + onReceive: () => void; }) { const { t } = useTranslation(); return (
  • - + {tokenName.toUpperCase() == "USDT" && } + {tokenName.toUpperCase() == "RMOB" && }
    {tokenName} @@ -308,17 +441,14 @@ function ReceiveCom({ {t("待领取")} {toReceive}
    -
    { - Toast.show({ - content: t("领取成功,前往钱包查看"), - icon: "success", - }); - }} + onClick={() => onReceive()} + fill="outline" + disabled={toReceive == 0} > {t("领取")} -
    +
  • ); diff --git a/src/pages/InvitationList.tsx b/src/pages/InvitationList.tsx index cf86e5c..9104828 100644 --- a/src/pages/InvitationList.tsx +++ b/src/pages/InvitationList.tsx @@ -1,32 +1,24 @@ /* * @LastEditors: John * @Date: 2024-06-19 11:03:01 - * @LastEditTime: 2024-06-19 17:33:25 + * @LastEditTime: 2024-06-22 10:27:19 * @Author: John */ -import { shortenString } from "@/utils"; -import Ellipsis from "antd-mobile/es/components/ellipsis"; +import { api_preprelion_list } from "@/server/api"; +import { PreprelionListItem } from "@/server/module"; +import { Empty } from "antd-mobile"; +import { useEffect, useState } from "react"; import DataTable, { TableColumn } from "react-data-table-component"; import { useTranslation } from "react-i18next"; -import { useAccount } from "wagmi"; -interface DataRow { - id: number; - address: string; - level: string; - nft: number; -} export default function () { - const { address } = useAccount(); const { t } = useTranslation(); - const columns: TableColumn[] = [ + const [data, setData] = useState([]); + const columns: TableColumn[] = [ { name: t("地址"), selector: (row) => row.address, grow: 4, - // cell(row, rowIndex, column, id) { - // return ; - // }, }, { name: t("级别"), @@ -35,43 +27,25 @@ export default function () { }, { name: t("直推NFT"), - selector: (row) => row.nft, + selector: (row) => row.mintNumber, // @ts-ignore right: "true", grow: 2, }, ]; - const data: DataRow[] = [ - { - id: 1, - address: "0x1000.....2223", - level: t("非活跃普通"), - nft: 0, - }, - { - id: 2, - address: "0x1000.....2223", - level: t("活跃普通"), - nft: 2, - }, - { - id: 3, - address: "0x1000.....2223", - level: t("社长"), - nft: 23, - }, - { - id: 4, - address: "0x1000.....2223", - level: t("基金会社长"), - nft: 50, - }, - ]; + useEffect(() => { + (async () => { + const { data } = await api_preprelion_list().send({}); + setData(data?.data || []); + })(); + + return () => {}; + }, []); return ( <> - + } /> ); } diff --git a/src/pages/LevelUp.module.css b/src/pages/LevelUp.module.css index db828a5..139a96c 100644 --- a/src/pages/LevelUp.module.css +++ b/src/pages/LevelUp.module.css @@ -34,6 +34,7 @@ box-sizing: border-box; span { + white-space: nowrap; &:nth-of-type(1) { /* 自动布局子元素 */ opacity: 1; diff --git a/src/pages/LevelUp.tsx b/src/pages/LevelUp.tsx index 3d2558b..6a44d35 100644 --- a/src/pages/LevelUp.tsx +++ b/src/pages/LevelUp.tsx @@ -1,12 +1,88 @@ import IconFont from "@/components/iconfont"; import classes from "./LevelUp.module.css"; -import { cn } from "@/utils"; +import { cn, getLevelName } from "@/utils"; import Button from "antd-mobile/es/components/button"; import Space from "antd-mobile/es/components/space"; -import { PropsWithChildren } from "react"; +import { PropsWithChildren, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { + api_get_user_upgrade_information, + api_upgrade, + api_users_cancel_orders, +} from "@/server/api"; +import { UpgradeOrder, UserUpgradeInformation } from "@/server/module"; +import Toast, { ToastHandler } from "antd-mobile/es/components/toast"; +import { + authorizedU, + getApproveUsdt, + getBalance, + upGradeByContract, +} from "@/contract/utils"; +import { toWei } from "web3-utils"; +import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus"; +import { BaseError } from "wagmi"; +import { Dialog } from "antd-mobile"; +import { useNavigate } from "react-router-dom"; export default function () { const { t } = useTranslation(); + const navigate = useNavigate(); + const [userUpgradeInfo, setUserUpgradeInfo] = + useState(); + const [approveUsdt, setApproveUsdt] = useState(0n); + const [balance, setBalance] = useState(0n); + const approveLoadingToast = useRef(); + const approvePrice = useMemo( + () => BigInt(toWei(userUpgradeInfo?.price || "0", "ether")), + [userUpgradeInfo?.price] + ); + const upgradeLoadingtoast = useRef(); + const orderInfo = useRef(); + + const { transcationStatus, startPollingCheckBuyStatus } = + usePollingCheckBuyStatus("NORMAL"); + useEffect(() => { + updateUserUpgrdeInfo(); + return () => {}; + }, []); + + useEffect(() => { + (async () => { + if (userUpgradeInfo?.status != 1) return; + Toast.show({ icon: "loading", content: t("正在获取已授权金额") }); + setBalance(await getBalance()); + setApproveUsdt(await getApproveUsdt()); + Toast.clear(); + })(); + + return () => {}; + }, [userUpgradeInfo?.status]); + + useEffect(() => { + console.log("approvePrice:", approvePrice); + + return () => {}; + }, [approvePrice]); + + useEffect(() => { + if (transcationStatus == "success") { + upgradeLoadingtoast.current?.close(); + Dialog.alert({ + content: `${t("升级成功,返回首页查看")}`, + confirmText: "OK", + onConfirm() { + navigate("/"); + }, + }); + } + + return () => {}; + }, [transcationStatus]); + + async function updateUserUpgrdeInfo() { + const { data } = await api_get_user_upgrade_information().send({}); + setUserUpgradeInfo(data?.data); + } + return ( <>
    @@ -14,7 +90,7 @@ export default function () {
    {t("当前级别")} - {t("普通活跃")} + {getLevelName(userUpgradeInfo?.level || 0)}
    {t("提升级别")} - {t("社长")} + {userUpgradeInfo?.level == 1 ? t("社长") : t("无")}
    {t("当前升级价格:")} - 110 USDT + {userUpgradeInfo?.price || 0} USDT
    {t("价格说明:")} {t( - "升级费用100USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加10%,既第二位社长升级铸造费用为100u+100*10%=110USDT,以此类推。" + "升级费用xxx USDT起,其中gas费2USDT,剩余部分50%进入资金池,另外50%平均分给所有升级成功的社长和基金会社长。自第二个社长升级开始,每升级一名社长所需铸造费用增加xxx,既第二位社长升级铸造费用为xxxu+xxx*xxx=xxx USDT,以此类推。", + { + value1: userUpgradeInfo?.upgradeFees || 0, + value2: userUpgradeInfo?.proportion || "0%", + value3: + parseFloat(userUpgradeInfo?.upgradeFees || "0") + + (parseFloat(userUpgradeInfo?.upgradeFees || "0") * + parseFloat( + (userUpgradeInfo?.proportion || "0%").replace("%", "") + )) / + 100, + } )}
    -
    @@ -55,7 +202,9 @@ export default function () {
      (); + const [approveUsdt, setApproveUsdt] = useState(0n); + const [balance, setBalance] = useState(0n); + const orderInfo = useRef(); + const navigate = useNavigate(); + + const buyLoadingToast = useRef(); + const approveLoadingToast = useRef(); + const { buyNftIds, startPollingCheckBuyStatus } = + usePollingCheckBuyStatus("NFT"); + + const approvePrice = useMemo( + () => BigInt(toWei(nftConfig?.nftPrice || "0", "ether")), + [nftConfig?.nftPrice] + ); + + useEffect(() => { + updateNftConfig(); + + return () => {}; + }, []); + + useEffect(() => { + (async () => { + Toast.show({ icon: "loading", content: t("正在获取已授权金额") }); + setBalance(await getBalance()); + setApproveUsdt(await getApproveUsdt()); + Toast.clear(); + })(); + + return () => {}; + }, [Token]); + + async function updateNftConfig() { + const { data } = await api_get_nft_configuration_data().send({}); + setNftConfig(data?.data); + } + useEffect(() => { + if (buyNftIds) { + buyLoadingToast.current?.close(); + Dialog.alert({ + content: `${t("MINT成功,返回首页查看")}`, + confirmText: "OK", + onConfirm() { + navigate("/"); + }, + }); + } + + return () => {}; + }, [buyNftIds]); + + useEffect(() => { + return () => {}; + }, []); + return ( <>
      @@ -22,17 +99,17 @@ export default function () {
      • {t("NFT总量:")} - 20000 + {nftConfig?.nftCount || 0}
      • {t("MINT余量:")} - 15000 + {nftConfig?.nftRemainder || 0}
      • {t("当前MINT价格:")} - 110 USDT + {nftConfig?.nftPrice || 0} USDT
      @@ -40,16 +117,87 @@ export default function () { {t("价格说明:")} {t( - "100USDT起,每增加100名普通会员,NFT价格上涨1%,既:前100名价格为100USDT/枚,101-200名价格为100u+100u*1%=101USDT/枚,以此类推。" + "{{value1}} USDT起,每增加 {{value2}} 名普通会员,NFT价格上涨 {{value3}},既:前 {{value2}} 名价格为 {{value1}} USDT/枚,value4 名价格为{{value1}}u+{{value1}}u*{{value3}}={{value5}} USDT/枚,以此类推。", + { + value1: nftConfig?.initialPrice || 0, + value2: nftConfig?.floatingQuantity || 0, + value3: nftConfig?.kamibutsu || "0%", + value4: `${ + parseInt(nftConfig?.floatingQuantity || "0") + 1 + } - ${parseInt(nftConfig?.floatingQuantity || "0") * 2}`, + value5: + parseFloat(nftConfig?.initialPrice || "0") + + (parseFloat(nftConfig?.initialPrice || "0") * + parseFloat( + (nftConfig?.kamibutsu || "0%").replace("%", "") + )) / + 100, + } )}
      - diff --git a/src/server/api.ts b/src/server/api.ts index e69de29..56dbe8e 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -0,0 +1,133 @@ +/* + * @LastEditors: John + * @Date: 2024-06-18 10:28:21 + * @LastEditTime: 2024-06-22 11:43:04 + * @Author: John + */ +import { GET, POST } from "./client"; +import { + ClaimIncome, + IncomeRecord, + IncomeRecordType, + NftConfigurationData, + NftOrder, + PreprelionListItem, + UpgradeOrder, + UserHomeData, + UserIncome, + UserUpgradeInformation, +} from "./module"; + +// 检查账号是否注册 +export function api_check_account_registration() { + return GET<{ account: string }, { exist: boolean }>({ + url: "/api/account/exist", + requiresToken: false, + }); +} + +// 登录 +export function api_login() { + return POST< + { + account: `0x${string}` | undefined; + password: string; + publicKey: string; + chainType: 2; + }, + { token: string } + >({ + url: "/api/account/signIn", + requiresToken: false, + }); +} + +// 注册 +export function api_signUp() { + return POST< + { + account: `0x${string}` | undefined; + publicKey: string; + shareCode: string; + chainType: 2; + }, + any + >({ url: "/api/account/signUp", requiresToken: false }); +} + +// 获取钱包签名串 +export function api_get_wallet_signature_string() { + return GET< + { account: `0x${string}` | undefined }, + { encryptedString: string } + >({ + url: "/api/account/randomCode", + requiresToken: false, + }); +} + +// 获取首页用户数据 +export function api_get_homepage_user_data() { + return GET({ + url: "/api/common/getUserData", + requiresToken: false, + requiresAddress: false, + }); +} + +// 获取用户升级信息 +export function api_get_user_upgrade_information() { + return GET({ + url: "/api/user/userUpgradeInformation", + }); +} + +// 获取NFT配置数据 +export function api_get_nft_configuration_data() { + return GET({ + url: "/api/nft/getUserData", + }); +} + +// 分页查询收益记录 +export function api_pagling_query_income_record() { + return GET< + { + status?: IncomeRecordType; + id: string; + type: 1 | 2; // 1=领取记录 2=发放记录 + pageNum: number; + pageSize: number; + }, + IncomeRecord + >({ + url: "/api/common/earningsRecords", + }); +} + +// NFT下单 +export function api_nft_order() { + return POST({ url: "/api/nft/payNft" }); +} + +// 用户取消订单告诉我 +export function api_users_cancel_orders() { + return POST({ url: "/api/nft/cancel" }); +} + +// 直推列表 +export function api_preprelion_list() { + return GET({ url: "/api/user/getDirectPushList" }); +} + +// 升级 +export function api_upgrade() { + return POST({ url: "/api/user/upgrade" }); +} + +// 用户领取收益 +export function api_claim_income() { + return POST({ + url: "/api/common/claimYourEarnings", + }); +} diff --git a/src/server/client.ts b/src/server/client.ts index 268dc5f..739dcee 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -1,34 +1,132 @@ /* * @LastEditors: John * @Date: 2024-06-18 10:09:21 - * @LastEditTime: 2024-06-18 10:27:04 + * @LastEditTime: 2024-06-21 14:47:26 * @Author: John */ import { Client } from "@hyper-fetch/core"; import { BASE_RESPONSE } from "./module"; +import useUserStore from "@/store/User"; +import { getAccount, connect } from "@wagmi/core"; +import { config } from "@/components/WalletProvider"; +import { Lang } from "@/constants"; +import { Toast } from "antd-mobile"; +import { injected } from "wagmi/connectors"; +import { signAndLogin } from "@/utils/wallet"; +import i18next from "i18next"; +function initClient({ + requiresToken, + requiresAddress, +}: { + requiresToken: boolean; + requiresAddress: boolean; +}) { + return new Client({ url: import.meta.env.VITE_BASE_API_URL }) + .onAuth(async (req) => { + if (requiresToken) { + if (!useUserStore.getState().Token) { + // TODO 登录获取token + // Toast.show({ content: "token is emtiy!", icon: "fail" }); + if (!getAccount(config).address) + await connect(config, { connector: injected() }); + await signAndLogin(getAccount(config).address!); + } + } -const client = new Client({ url: import.meta.env.VITE_BASE_API_URL }) - .onAuth((req) => { - return req; - }) - .onRequest((req) => { - req.setHeaders({}); - return req; - }) - .onResponse((res) => { - return res; - }); + if (requiresAddress) { + if (!getAccount(config).address) { + // TODO 链接钱包 + // Toast.show({ content: "address is emtiy!", icon: "fail" }); + await connect(config, { connector: injected() }); + } + } -export const POST = ({ url }: { url: string }) => { - return client.createRequest, P>()({ + const headers = { + ...req.headers, + Authorization: useUserStore.getState().Token, + "Accept-Language": getAcceptLang(), + address: (getAccount(config).address || "") as string, + }; + return req.setHeaders(headers); + }) + .onResponse((res) => { + console.log(res); + if (!res.success) { + Toast.clear(); + Toast.show({ content: i18next.t("服务器错误"), icon: "fail" }); + throw new Error(res.error?.message); + } + const resData: BASE_RESPONSE = res.data; + if (resData.code !== 200 && resData.code !== 0) { + if (resData.msg) Toast.show({ content: resData.msg, icon: "fail" }); + throw new Error(resData.msg || "client on response error"); + } + return res; + }); +} + +export const POST =

      ({ + url, + requiresToken = true, + requiresAddress = true, +}: { + url: string; + requiresToken?: boolean; + requiresAddress?: boolean; +}) => { + return initClient({ requiresToken, requiresAddress }).createRequest< + BASE_RESPONSE, + P, + any, + QueryParams + >()({ method: "POST", endpoint: url, }); }; -export const GET = ({ url }: { url: string }) => { - return client.createRequest, any, any, P>()({ +export const GET =

      ({ + url, + requiresToken = true, + requiresAddress = true, +}: { + url: string; + requiresToken?: boolean; + requiresAddress?: boolean; +}) => { + return initClient({ requiresToken, requiresAddress }).createRequest< + BASE_RESPONSE, + any, + any, + P + >()({ method: "GET", endpoint: url, }); }; + +function getAcceptLang() { + let apiAcceptLang; + switch (useUserStore.getState().Lang) { + case Lang.cn: + apiAcceptLang = "zh-CN"; + break; + case Lang.tw: + apiAcceptLang = "zh-TW"; + break; + case Lang.en: + apiAcceptLang = "en-US"; + break; + case Lang.de: + apiAcceptLang = "de-DE"; + break; + case Lang.jp: + apiAcceptLang = "ja-JP"; + break; + default: + apiAcceptLang = "zh-CN"; + break; + } + + return apiAcceptLang; +} diff --git a/src/server/module.d.ts b/src/server/module.d.ts index 42d7aaf..5ed2137 100644 --- a/src/server/module.d.ts +++ b/src/server/module.d.ts @@ -1,6 +1,137 @@ export type BASE_RESPONSE = { code: 0 | 200; - data: T | null; + data: T; msg: string; timeMillis: number; }; // What's returned from request + +export type Level = 0 | 1 | 2 | 3; // 0=无等级 1=会员 2=社长 3=基金会 +export interface UserHomeData { + address: string; + airdropNumber: number; + foundation: number; + invitationCode: string; + level: Level; + mintNumber: number; + nftId: number; + pools: string; + president: number; + presidentNumber: number; + userImg: string; + userIncomes: UserIncome[]; +} +export interface UserIncome { + coinId: number; + coinName: string; + collection: number; + createTime: string; + flag: number; + id: number; + receive: string; + updateTime: string; + userId: number; +} + +export interface UserUpgradeInformation { + foundation: number; + level: Level; + numberOfPresidents: number; + ordinary: number; + president: number; + price: string; + proportion: string; + status: 1 | 0; //1=可升级 0=不可升级 + upgradeFees: string; +} + +export interface NftConfigurationData { + floatingQuantity: string; + initialPrice: string; + kamibutsu: string; + nftCount: number; + nftPrice: string; + nftRemainder: number; +} + +export interface IncomeRecord { + countId: string; + current: number; + maxLimit: string; + optimizeCountSql: boolean; + orders: { asc: boolean; column: string }[]; + pages: number; + records: IncomeRecordsItem[]; + searchCount: boolean; + size: number; + total: number; +} + +export interface IncomeRecordsItem { + id: number; + createTime: string; + updateTime: string; + flag: number; + userId: number; + incomeId: number; + type: 1 | 2 | 3 | 4; // 1:领取成功,2发放记录 3,领取中 4:已取消 + coinId: number; + opType: number; + opRemark: string; + opBefore: number; + opValue: number; + opAfter: number; + extRemark: string; +} + +export type IncomeRecordType = 4 | 5 | 6 | 7 | 8 | 9; //4=直推>20NFT,5=升级费平分,6=NFT空投,7=社长空投,8=基金会社长,9=直推空投 + +export interface NftOrder { + address: string; + buyAmount: string; + buyCount: string; + createBy: string; + createTime: string; + hash: string; + id: number; + illustrate: string; + nftId: number; + orderNumber: string; + payCoin: string; + recommendId: number; + status: number; + updateBy: string; + updateTime: string; + userId: number; +} + +export interface PreprelionListItem { + address: string; + level: number; + mintNumber: number; + userType: number; +} + +export interface UpgradeOrder { + address: string; + buyAmount: string; + createBy: string; + createTime: string; + endLevel: number; + hash: string; + id: number; + illustrate: string; + orderNumber: string; + payCoin: string; + startLevel: number; + status: number; + updateBy: string; + updateTime: string; + userId: number; +} + +export interface ClaimIncome { + claimQuantity: string; + hash: string; + orderNumber: string; + time: string; +} diff --git a/src/store/User.ts b/src/store/User.ts index 66031db..0a5ec94 100644 --- a/src/store/User.ts +++ b/src/store/User.ts @@ -1,7 +1,7 @@ /* * @LastEditors: John * @Date: 2024-06-17 17:45:43 - * @LastEditTime: 2024-06-19 16:26:24 + * @LastEditTime: 2024-06-20 15:49:09 * @Author: John */ import { ASYNC_STORAGE_KEY, Lang } from "@/constants"; @@ -9,21 +9,33 @@ import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; interface UserState { + Address: string; + UpdateAddress: (a: string) => void; + Token: string; UpdateToken: (t: string) => void; Lang: Lang; UpdateLang: (l: Lang) => void; + + InviteCode: string; + UpdateInviteCode: (I: string) => void; } export const useUserStore = create()( persist( (set, _get) => ({ + Address: "", + UpdateAddress: (a) => set({ Address: a }), + Token: "", UpdateToken: (t) => set({ Token: t }), Lang: Lang.en, UpdateLang: (l) => set({ Lang: l }), + + InviteCode: "", + UpdateInviteCode: (i) => set({ InviteCode: i }), }), { name: ASYNC_STORAGE_KEY.Store, // name of item in the storage (must be unique) diff --git a/src/style/react-data-table-component-cover-m.css b/src/style/react-data-table-component-cover-m.css index 12f3c9f..9054f4a 100644 --- a/src/style/react-data-table-component-cover-m.css +++ b/src/style/react-data-table-component-cover-m.css @@ -2,6 +2,9 @@ width: 100vw !important; background-color: transparent !important; padding: 0 14px; + > div { + background-color: transparent !important; + } .rdt_TableHead { .rdt_TableHeadRow { diff --git a/src/utils/index.ts b/src/utils/index.ts index c08f2dc..954ec1c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,13 +1,14 @@ /* * @LastEditors: John * @Date: 2024-06-17 18:19:27 - * @LastEditTime: 2024-06-19 17:07:43 + * @LastEditTime: 2024-06-22 15:26:37 * @Author: John */ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; import Toast from "antd-mobile/es/components/toast"; import i18next from "i18next"; +import { Level } from "@/server/module"; export const ua = navigator.userAgent; export const isIOS = /iphone|ipad|ipod|ios/i.test(ua); @@ -83,3 +84,18 @@ export function copyText(text: string) { export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function getLevelName(level: Level) { + switch (level) { + case 0: + return i18next.t("普通非活跃"); + case 1: + return i18next.t("普通活跃"); + case 2: + return i18next.t("社长"); + case 3: + return i18next.t("基金会社长"); + default: + break; + } +} diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index e3c3774..5a6bc98 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -1,20 +1,32 @@ /* * @LastEditors: John * @Date: 2024-06-19 15:55:07 - * @LastEditTime: 2024-06-19 15:56:45 + * @LastEditTime: 2024-06-20 16:30:22 * @Author: John */ import { config } from "@/components/WalletProvider"; +import { + api_check_account_registration, + api_get_wallet_signature_string, + api_login, + api_signUp, +} from "@/server/api"; +import useUserStore from "@/store/User"; import { writeContract, readContract, estimateGas, waitForTransactionReceipt, getConnectorClient, + signMessage, getChains, switchChain, getChainId, + disconnect, + getAccount, } from "@wagmi/core"; +import Toast from "antd-mobile/es/components/toast"; +import i18next from "i18next"; /** * @description: 检测网络并切换 @@ -55,3 +67,91 @@ export function checkNetWork(): Promise { } }); } + +// 签名并且登录 +export async function signAndLogin(address?: `0x${string}`): Promise { + return new Promise(async (reslove) => { + if (!address) return loginOut(); + if (address != useUserStore.getState().Address) { + useUserStore.setState((state) => { + return { ...state, Address: address, Token: "" }; + }); + } + + if (useUserStore.getState().Token) return reslove(); // token存在无需登录 + const publicKey = + "0305ef2a74bff2e2d68764c557ce2daecac92caa7a9406e3a90c2cf7c5b444a154"; + + const loadingToast = Toast.show({ + icon: "loading", + content: i18next.t("链接钱包中..."), + duration: 0, + maskClickable: false, + }); + + const { data: isExitData } = await api_check_account_registration().send({ + queryParams: { account: address }, + }); + if (isExitData?.data?.exist) { + // 登录 + + const { data: signatureData } = + await api_get_wallet_signature_string().send({ + queryParams: { account: address }, + }); + + let sign: string; + try { + sign = await signMessage(config, { + message: signatureData?.data?.encryptedString || "", + }); + } catch (error) { + // 用户拒绝签名或者遇到错误,断开链接 + disconnect(config); + loadingToast.close(); + loginOut(); + throw new Error("用户拒绝签名或者遇到错误,断开链接"); + } + + // TODO 登录✔ + const { data: loginInfoData } = await api_login().send({ + data: { + account: address, + password: sign, + publicKey, + chainType: 2, + }, + }); + + if (loginInfoData) { + useUserStore.setState((state) => { + return { ...state, Token: loginInfoData.data?.token }; + }); + + // TODO 判断用户是否绑定关系✔ + // await checkUserBind(false); + reslove(); + loadingToast.close(); + } + } else { + // 注册 + await api_signUp().send({ + data: { + account: address, + publicKey, + shareCode: "", + chainType: 2, + }, + }); + await signAndLogin(address); + reslove(); + loadingToast.close(); + } + }); +} + +export function loginOut() { + useUserStore.setState((state) => { + return { ...state, Address: "", Token: "" }; + }); +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 171ee5d..592dd9f 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,9 +1,17 @@ +/* + * @LastEditors: John + * @Date: 2024-06-17 17:20:03 + * @LastEditTime: 2024-06-21 13:50:16 + * @Author: John + */ /// interface ImportMetaEnv { + readonly VITE_BASE_URL: string; readonly VITE_BASE_API_URL: string; readonly VITE_PARTICIPATE_CHAIN_ID: number; readonly VITE_NETWORK_USDT_ADDRESS: `0x${string}`; + readonly VITE_PURCHASED_CONTRACT_ADDRESS: `0x${string}`; readonly VITE_CHECK_TRANSACTION_DETAILS_URL: string; // 更多环境变量... readonly MODE: "development" | "production" | "test"; diff --git a/vite.config.ts b/vite.config.ts index c3f9fea..7acc4e3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,7 @@ /* * @LastEditors: John * @Date: 2024-06-17 17:20:03 - * @LastEditTime: 2024-06-19 17:41:43 + * @LastEditTime: 2024-06-24 10:08:56 * @Author: John */ import { defineConfig } from "vite"; @@ -14,6 +14,13 @@ import { nodePolyfills } from "vite-plugin-node-polyfills"; export default defineConfig({ server: { host: "192.168.10.167", + proxy: { + "/dev": { + target: "http://192.168.10.106:8100", + changeOrigin: true, + rewrite: (path) => path.replace(/^\/dev/, ""), + }, + }, }, plugins: [ react(), diff --git a/yarn.lock b/yarn.lock index 744bc21..ff2503f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2727,7 +2727,7 @@ __metadata: languageName: node linkType: hard -"ahooks@npm:^3.7.6": +"ahooks@npm:^3.7.6, ahooks@npm:^3.8.0": version: 3.8.0 resolution: "ahooks@npm:3.8.0" dependencies: @@ -6957,6 +6957,7 @@ __metadata: "@typescript-eslint/parser": "npm:^7.2.0" "@vitejs/plugin-react": "npm:^4.2.1" "@web3modal/wagmi": "npm:^5.0.2" + ahooks: "npm:^3.8.0" antd-mobile: "npm:^5.36.1" autoprefixer: "npm:^10.4.19" clsx: "npm:^2.1.1" @@ -6982,6 +6983,7 @@ __metadata: vite-plugin-compression: "npm:^0.5.1" vite-plugin-node-polyfills: "npm:^0.22.0" wagmi: "npm:^2.10.2" + web3-utils: "npm:^4.3.0" zustand: "npm:^4.5.2" languageName: unknown linkType: soft @@ -8226,6 +8228,48 @@ __metadata: languageName: node linkType: hard +"web3-errors@npm:^1.2.0": + version: 1.2.0 + resolution: "web3-errors@npm:1.2.0" + dependencies: + web3-types: "npm:^1.6.0" + checksum: 10c0/3028ef33ba50f4441e02ff47f56afec226c6946bbb019dbf6260760b7d5980bb42cf9b1ff497ac3442ed498c386e5ca37a7ad20e71b3482e6b3b90b70f1b5249 + languageName: node + linkType: hard + +"web3-types@npm:^1.6.0": + version: 1.7.0 + resolution: "web3-types@npm:1.7.0" + checksum: 10c0/0da724b67911d76139b704406107bde624c524a04bfe749808a3e137e06078ac1c52c305eedb521b84d67363932d214ba54d7851394a8e7b425e17de5ef813e4 + languageName: node + linkType: hard + +"web3-utils@npm:^4.3.0": + version: 4.3.0 + resolution: "web3-utils@npm:4.3.0" + dependencies: + ethereum-cryptography: "npm:^2.0.0" + eventemitter3: "npm:^5.0.1" + web3-errors: "npm:^1.2.0" + web3-types: "npm:^1.6.0" + web3-validator: "npm:^2.0.6" + checksum: 10c0/884e553cacd8009440dcbd1ba80516fadf054b558aa974228f71e20fb2636afab94c3af0d386e88fb662245114ff5d3b251d45bb9b35f1fad8b20f6e1ef49743 + languageName: node + linkType: hard + +"web3-validator@npm:^2.0.6": + version: 2.0.6 + resolution: "web3-validator@npm:2.0.6" + dependencies: + ethereum-cryptography: "npm:^2.0.0" + util: "npm:^0.12.5" + web3-errors: "npm:^1.2.0" + web3-types: "npm:^1.6.0" + zod: "npm:^3.21.4" + checksum: 10c0/28728773b9abad2531f7a4145784db56ec9ecffeb25cc9f6fe67bedeb01a1833b1a5d1a2e0f431ce4a3c8c6f13b111f35202dd8fa0829c6e2fcd68c58d1d5658 + languageName: node + linkType: hard + "webextension-polyfill@npm:>=0.10.0 <1.0": version: 0.12.0 resolution: "webextension-polyfill@npm:0.12.0" @@ -8515,6 +8559,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.21.4": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 + languageName: node + linkType: hard + "zustand@npm:4.4.1": version: 4.4.1 resolution: "zustand@npm:4.4.1"