🎉 init:

This commit is contained in:
john 2024-06-27 17:00:45 +08:00
parent 2ae4082902
commit 6437428c05
39 changed files with 1062 additions and 2594 deletions

View File

@ -1,12 +1,12 @@
### ###
# @LastEditors: John # @LastEditors: John
# @Date: 2024-06-18 10:12:21 # @Date: 2024-06-18 10:12:21
# @LastEditTime: 2024-06-25 14:04:33 # @LastEditTime: 2024-06-27 15:34:18
# @Author: John # @Author: John
### ###
VITE_BASE_URL=http://192.168.10.167:5173/ VITE_BASE_URL=
VITE_BASE_API_URL=/dev VITE_BASE_API_URL=/dev
VITE_PARTICIPATE_CHAIN_ID=97 VITE_PARTICIPATE_CHAIN_ID=97
VITE_PURCHASED_CONTRACT_ADDRESS=0x7aAe4f2CA23482B58D6f9e8d1fBb5e413e7013c8 VITE_PURCHASED_CONTRACT_ADDRESS=0x3000782e8ba573eFaC3fe72930A963160e752B60
VITE_NETWORK_USDT_ADDRESS=0xf9A18B7FC8Eb118f8Ad59fBD6eb1A181eaCb4E63 VITE_NETWORK_USDT_ADDRESS=0x342436983fF95C38878248504Ea26d6724A51054
VITE_CHECK_TRANSACTION_DETAILS_URL=https://testnet.bscscan.com/ VITE_CHECK_TRANSACTION_DETAILS_URL=https://testnet.bscscan.com/

View File

@ -1,12 +1,12 @@
### ###
# @LastEditors: John # @LastEditors: John
# @Date: 2024-06-24 18:38:45 # @Date: 2024-06-24 18:38:45
# @LastEditTime: 2024-06-25 14:04:37 # @LastEditTime: 2024-06-27 14:35:41
# @Author: John # @Author: John
### ###
VITE_BASE_URL=http://wwwtest.exgo.pro VITE_BASE_URL=http://wwwtest.exgo.pro
VITE_BASE_API_URL=http://wwwtest.exgo.pro VITE_BASE_API_URL=http://wwwtest.exgo.pro
VITE_PARTICIPATE_CHAIN_ID=97 VITE_PARTICIPATE_CHAIN_ID=97
VITE_PURCHASED_CONTRACT_ADDRESS=0x7aAe4f2CA23482B58D6f9e8d1fBb5e413e7013c8 VITE_PURCHASED_CONTRACT_ADDRESS=0x3000782e8ba573eFaC3fe72930A963160e752B60
VITE_NETWORK_USDT_ADDRESS=0xf9A18B7FC8Eb118f8Ad59fBD6eb1A181eaCb4E63 VITE_NETWORK_USDT_ADDRESS=0x342436983fF95C38878248504Ea26d6724A51054
VITE_CHECK_TRANSACTION_DETAILS_URL=https://testnet.bscscan.com/ VITE_CHECK_TRANSACTION_DETAILS_URL=https://testnet.bscscan.com/

View File

@ -1,7 +1,7 @@
<!-- <!--
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-17 17:20:03 * @Date: 2024-06-17 17:20:03
* @LastEditTime: 2024-06-18 10:51:36 * @LastEditTime: 2024-06-26 15:41:11
* @Author: John * @Author: John
--> -->
<!DOCTYPE html> <!DOCTYPE html>
@ -13,7 +13,7 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0,maximum-scale=1, minimum-scale=1, user-scalable=no" content="width=device-width, initial-scale=1.0,maximum-scale=1, minimum-scale=1, user-scalable=no"
/> />
<title>red devils</title> <title>Edge AI Node</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -7,8 +7,8 @@ html,
} }
@font-face { @font-face {
font-family: "DM Sans"; font-family: "Space Grotesk";
src: url("./assets/font/DMSans_18pt-Medium.ttf") format("truetype"); src: url("./assets/font/SpaceGrotesk-Medium.ttf") format("truetype");
} }
* { * {

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-17 17:20:03 * @Date: 2024-06-17 17:20:03
* @LastEditTime: 2024-06-21 14:19:52 * @LastEditTime: 2024-06-26 16:00:00
* @Author: John * @Author: John
*/ */
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
@ -12,9 +12,6 @@ import Home from "./pages/Home";
import Header from "./components/Header"; import Header from "./components/Header";
import Mint from "./pages/Mint"; import Mint from "./pages/Mint";
import RouterLogProvider from "./context/RouterContext"; import RouterLogProvider from "./context/RouterContext";
import LevelUp from "./pages/LevelUp";
import AssetRecord from "./pages/AssetRecord";
import AirDropRecord from "./pages/AirDropRecord";
import InvitationList from "./pages/InvitationList"; import InvitationList from "./pages/InvitationList";
import { useEffect } from "react"; import { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -52,9 +49,6 @@ function App() {
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/mint" element={<Mint />} /> <Route path="/mint" element={<Mint />} />
<Route path="/levelup" element={<LevelUp />} />
<Route path="/assetrecord" element={<AssetRecord />} />
<Route path="/airdroprecord" element={<AirDropRecord />} />
<Route path="/invitationlist" element={<InvitationList />}></Route> <Route path="/invitationlist" element={<InvitationList />}></Route>
</Routes> </Routes>
</RouterLogProvider> </RouterLogProvider>

Binary file not shown.

View File

@ -22,7 +22,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -49,7 +49,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-18 15:16:31 * @Date: 2024-06-18 15:16:31
* @LastEditTime: 2024-06-26 14:49:22 * @LastEditTime: 2024-06-27 15:29:00
* @Author: John * @Author: John
*/ */
import Picker, { import Picker, {
@ -38,15 +38,9 @@ export default function () {
const navTitle = useMemo<string>(() => { const navTitle = useMemo<string>(() => {
switch (location.pathname) { switch (location.pathname) {
case "/mint": case "/mint":
return t("铸造 NFT"); return t("Buy Node");
case "/levelup":
return t("级别提升");
case "/assetrecord":
return t("收益记录");
case "/airdroprecord":
return t("RMOB记录");
case "/invitationlist": case "/invitationlist":
return t("直推列表"); return t("Invitation List");
default: default:
return t("返回"); return t("返回");
} }
@ -57,45 +51,9 @@ export default function () {
<div className={classes.header}> <div className={classes.header}>
<div className={classes.header_top}> <div className={classes.header_top}>
<img className={classes.header_logo} src={logo} alt="" /> <img className={classes.header_logo} src={logo} alt="" />
<span className={classes.header_title}>{t("红魔NFT")}</span> <span className={classes.header_title}>
<Popover.Menu {t("YOTTA Edge AI Node")}
mode="dark" </span>
actions={langColums}
placement="bottom"
onAction={(node) => {
switch (node.key) {
case Lang.en:
i18n.changeLanguage(Lang.en);
UpdateLang(Lang.en);
break;
case Lang.cn:
i18n.changeLanguage(Lang.cn);
UpdateLang(Lang.cn);
break;
case Lang.tw:
i18n.changeLanguage(Lang.tw);
UpdateLang(Lang.tw);
break;
case Lang.jp:
i18n.changeLanguage(Lang.jp);
UpdateLang(Lang.jp);
break;
case Lang.de:
i18n.changeLanguage(Lang.de);
UpdateLang(Lang.de);
break;
default:
break;
}
}}
trigger="click"
>
<IconFont
className={classes.header_lang}
name="diqiu"
color={"#fff"}
/>
</Popover.Menu>
</div> </div>
{location.pathname != "/" && ( {location.pathname != "/" && (

View File

@ -11,7 +11,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-17 18:01:43 * @Date: 2024-06-17 18:01:43
* @LastEditTime: 2024-06-24 18:51:52 * @LastEditTime: 2024-06-27 14:51:11
* @Author: John * @Author: John
*/ */
/* /*
@ -34,7 +34,7 @@ const projectId = "680d2de71bbabc3e79363f1e6b513a68";
// 2. Create wagmiConfig // 2. Create wagmiConfig
const metadata = { const metadata = {
name: "yotta-node", name: "yotta-node",
description: "red devils dapp", description: "Edge AI Node dapp",
url: import.meta.env.BASE_URL, // origin must match your domain & subdomain url: import.meta.env.BASE_URL, // origin must match your domain & subdomain
icons: [`${import.meta.env.BASE_URL}/favicon.svg`], icons: [`${import.meta.env.BASE_URL}/favicon.svg`],
}; };
@ -61,7 +61,7 @@ createWeb3Modal({
wagmiConfig: config, wagmiConfig: config,
projectId, projectId,
themeVariables: { themeVariables: {
"--w3m-accent": "#ea6d28", "--w3m-accent": "#2dfcfc",
}, },
featuredWalletIds: [ featuredWalletIds: [
...(window.ethereum ...(window.ethereum

View File

@ -1,451 +0,0 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "hongMoAddr",
"type": "address"
},
{
"internalType": "address",
"name": "payAddr",
"type": "address"
},
{
"internalType": "address",
"name": "p1",
"type": "address"
},
{
"internalType": "address",
"name": "p2",
"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": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "buyAddr",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paymentType",
"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"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "buyAddr",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
}
],
"name": "RewardSuccess",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "buyAddr",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
}
],
"name": "UpgradeRange",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "paymentType",
"type": "uint256"
}
],
"name": "buyHMNFT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
}
],
"name": "getOrderStatus",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"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": "payee1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "payee2",
"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": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "paymentTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "hashStr",
"type": "bytes32"
}
],
"name": "reward",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "setNFTAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
},
{
"internalType": "address",
"name": "b",
"type": "address"
}
],
"name": "setPayeeAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "p",
"type": "uint256"
}
],
"name": "setPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "setTokenIndex",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "setUSDCAddress",
"outputs": [],
"stateMutability": "nonpayable",
"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": [
{
"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"
},
{
"inputs": [],
"name": "usdc",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@ -0,0 +1,237 @@
[
{
"inputs": [
{
"internalType": "int256",
"name": "num",
"type": "int256"
}
],
"name": "AddIllegalTxCount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
},
{
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
}
],
"name": "buyNode",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_tokenAddress",
"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": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
},
{
"indexed": false,
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
},
{
"indexed": false,
"internalType": "uint256",
"name": "orderId",
"type": "uint256"
}
],
"name": "BuyNode",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "newTokenAddress",
"type": "address"
}
],
"name": "changeTokenAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"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": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "CurrentSoldNode",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "GetTimpstamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "IllegalTxCount",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_SELL_NODE",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-19 15:48:57 * @Date: 2024-06-19 15:48:57
* @LastEditTime: 2024-06-25 15:28:36 * @LastEditTime: 2024-06-27 11:31:09
* @Author: John * @Author: John
*/ */
import { config } from "@/components/WalletProvider"; import { config } from "@/components/WalletProvider";
@ -15,7 +15,7 @@ import {
import { encodeFunctionData } from "viem/utils"; import { encodeFunctionData } from "viem/utils";
import erc20Abi from "@/contract/abi/erc20abi.json"; import erc20Abi from "@/contract/abi/erc20abi.json";
import usdtAbi from "@/contract/abi/USDT.json"; import usdtAbi from "@/contract/abi/USDT.json";
import RedDevilsAbi from "@/contract/abi/RedDevils.json"; import edgeAiNodeAbi from "@/contract/abi/edgeAiNode.json";
import i18next from "i18next"; import i18next from "i18next";
import { BaseError } from "wagmi"; import { BaseError } from "wagmi";
@ -144,17 +144,22 @@ export const authorizedU = async (uNum: bigint) => {
* @returns * @returns
*/ */
export async function payByContract( export async function payByContract(
amount: bigint, recipients: string[],
amount: bigint[],
orderID: string, orderID: string,
payInduction: number timestamp: number
) { ) {
console.log("pay buy contract params", { amount, orderID }); console.log("pay buy contract params", { amount, orderID });
console.log("NETWORK_USDT:", import.meta.env.VITE_NETWORK_USDT_ADDRESS); console.log("NETWORK_USDT:", import.meta.env.VITE_NETWORK_USDT_ADDRESS);
return new Promise<string>(async (reslove, reject) => { return new Promise<string>(async (reslove, reject) => {
try { try {
let totalAmout = 0n;
for (let i = 0; i < amount.length; i++) {
totalAmout += amount[i];
}
const balance = await getBalance(); const balance = await getBalance();
if (balance < amount) { if (balance < totalAmout) {
console.log("用户代币余额不足"); console.log("用户代币余额不足");
reject(new BaseError(i18next.t("余额不足"))); reject(new BaseError(i18next.t("余额不足")));
return; return;
@ -162,27 +167,27 @@ export async function payByContract(
console.log("当前要授权的U:", amount); console.log("当前要授权的U:", amount);
let approvedU = await getApproveUsdt(); let approvedU = await getApproveUsdt();
if (approvedU < amount) { if (approvedU < totalAmout) {
await authorizedU(amount); await authorizedU(totalAmout);
} }
console.log("参数:", amount, orderID, payInduction); console.log("参数:", recipients, amount, orderID, timestamp);
estimateGas(config, { estimateGas(config, {
to: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, to: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS,
data: encodeFunctionData({ data: encodeFunctionData({
abi: RedDevilsAbi, abi: edgeAiNodeAbi,
functionName: "buyHMNFT", functionName: "buyNode",
args: [amount, orderID, payInduction], args: [recipients, amount, orderID, timestamp],
}), }),
}) })
.then((gas) => { .then((gas) => {
const gasPrice = (gas * 12n) / 10n; const gasPrice = (gas * 12n) / 10n;
console.log("estimate gas:%d , my gas: %d", gas, gasPrice); console.log("estimate gas:%d , my gas: %d", gas, gasPrice);
writeContract(config, { writeContract(config, {
abi: RedDevilsAbi, abi: edgeAiNodeAbi,
address: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS, address: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS,
functionName: "buyHMNFT", functionName: "buyNode",
args: [amount, orderID, payInduction], args: [recipients, amount, orderID, timestamp],
gas: gasPrice, gas: gasPrice,
}) })
.then((receipt) => { .then((receipt) => {
@ -190,12 +195,12 @@ export async function payByContract(
reslove(receipt); reslove(receipt);
}) })
.catch((err: BaseError) => { .catch((err: BaseError) => {
console.log("buyHMNFT Transaction err", err); console.log("buyNode Transaction err", err);
reject(err); reject(err);
}); });
}) })
.catch((err: BaseError) => { .catch((err: BaseError) => {
console.log("buyHMNFT estimateGas err", err); console.log("buyNode estimateGas err", err);
reject(err); reject(err);
}); });
} catch (err) { } catch (err) {
@ -204,121 +209,3 @@ export async function payByContract(
} }
}); });
} }
/**
* 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<string>(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);
reject(err);
});
})
.catch((err: BaseError) => {
console.log("upgradePrivilege estimateGas err", err);
reject(err);
});
} catch (err) {
reject(new BaseError(`${err}`));
}
});
}
/**
* receiveByContract
* @param amount
* @param orderID
* @returns
*/
export async function receiveByContract(
amount: bigint,
paymentTime: number,
orderID: string,
hashStr: string
) {
console.log("pay buy contract params", { amount, orderID });
console.log("NETWORK_USDT:", import.meta.env.VITE_NETWORK_USDT_ADDRESS);
return new Promise<string>(async (reslove, reject) => {
try {
console.log("参数:", amount, paymentTime, orderID, hashStr);
estimateGas(config, {
to: import.meta.env.VITE_PURCHASED_CONTRACT_ADDRESS,
data: encodeFunctionData({
abi: RedDevilsAbi,
functionName: "reward",
args: [amount, paymentTime, orderID, hashStr],
}),
})
.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: "reward",
args: [amount, paymentTime, orderID, hashStr],
gas: gasPrice,
})
.then((receipt) => {
console.log("write contract success!, receipt:", receipt);
reslove(receipt);
})
.catch((err: BaseError) => {
console.log("reward Transaction err", err);
reject(err);
});
})
.catch((err: BaseError) => {
console.log("reward estimateGas err", err);
reject(err);
});
} catch (err) {
reject(new BaseError(`${err}`));
}
});
}

37
src/hook/usePagination.ts Normal file
View File

@ -0,0 +1,37 @@
import { useRef, useState } from "react";
export default function usePagination<T = any>({
service,
}: {
service: ({
pageNum,
pageSize,
}: {
pageNum: number;
pageSize: number;
}) => Promise<T[]>;
}) {
const pageNum = useRef<number>(0);
const [hasMore, setHasMore] = useState(true);
const [list, setList] = useState<Awaited<ReturnType<typeof service>>>([]);
async function loadMore() {
if (!hasMore) return;
const pageSize = 20;
pageNum.current++;
const newList = await service({
pageNum: pageNum.current,
pageSize,
});
if (newList.length < pageSize) setHasMore(false);
setList([...list, ...newList]);
}
return {
hasMore,
list,
loadMore,
};
}

View File

@ -1,16 +1,12 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-18 10:30:43 * @Date: 2024-06-18 10:30:43
* @LastEditTime: 2024-06-19 16:55:26 * @LastEditTime: 2024-06-26 15:36:48
* @Author: John * @Author: John
*/ */
import i18next from "i18next"; import i18next from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import en from "./translation/en.json"; import en from "./translation/en.json";
import cn from "./translation/cn.json";
import tw from "./translation/tw.json";
import jp from "./translation/jp.json";
import de from "./translation/de.json";
i18next.use(initReactI18next).init({ i18next.use(initReactI18next).init({
compatibilityJSON: "v3", // <--- add this line. compatibilityJSON: "v3", // <--- add this line.
lng: "en", // if you're using a language detector, do not define the lng option lng: "en", // if you're using a language detector, do not define the lng option
@ -19,18 +15,6 @@ i18next.use(initReactI18next).init({
en: { en: {
translation: en, translation: en,
}, },
cn: {
translation: cn,
},
tw: {
translation: tw,
},
jp: {
translation: jp,
},
de: {
translation: de,
},
}, },
// if you see an error like: "Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz" // if you see an error like: "Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz"
// set returnNull to false (and also in the i18next.d.ts options) // set returnNull to false (and also in the i18next.d.ts options)

View File

@ -1,107 +0,0 @@
{
"AppName": "红魔",
"红魔NFT": "红魔NFT",
"铸造 NFT": "铸造 NFT",
"级别提升": "级别提升",
"收益记录": "收益记录",
"RMOB记录": "RMOB记录",
"直推列表": "直推列表",
"返回": "返回",
"选择语言": "选择语言",
"确定": "确定",
"取消": "取消",
"普通非活跃": "普通非活跃",
"升级": "升级",
"链接钱包": "链接钱包",
"邀请铸造": "邀请铸造",
"团队社长": "团队社长",
"邀请空投": "邀请空投",
"收益": "收益",
"Min结束后按照规则进行空投。": "Min结束后按照规则进行空投。",
"铸造 NFT 获得代币空投": "铸造 NFT 获得代币空投",
"总收益= 已领取 + 待领取": "总收益= 已领取 + 待领取",
"邀请": "邀请",
"邀请链接": "邀请链接",
"普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。": "普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。",
"数据披露": "数据披露",
"资金池": "资金池",
"社长席位": "社长席位",
"基金会社长席位": "基金会社长席位",
"待领取": "待领取",
"领取": "领取",
"钱包未链接,无法向您显示 NFT": "钱包未链接,无法向您显示 NFT",
"邀请列表": "邀请列表",
"NFT总量": "NFT总量",
"MINT余量": "MINT余量",
"当前MINT价格": "当前MINT价格",
"价格说明:": "价格说明:",
"{{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",
"当前级别": "当前级别",
"普通活跃": "普通活跃",
"提升级别": "提升级别",
"社长": "社长",
"当前升级价格:": "当前升级价格:",
"升级费用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以此类推。",
"升级条件": "升级条件",
"普通会员": "普通会员",
"(限量xxx个)": "(限量 {{value}} 个)",
"1. MINT一枚NFT成为非活跃普通会员": "1. MINT一枚NFT成为非活跃普通会员",
"2. MINT NFT并推荐MINT成为活跃普通会员": "2. MINT NFT并推荐MINT成为活跃普通会员",
"3. 享1%代币空投平分(所有普通会员)": "3. 享1%代币空投平分(所有普通会员)",
"4. 活跃普通会员根据推荐MINT数量额外有空投权益。": "4. 活跃普通会员根据推荐MINT数量额外有空投权益。",
"(限量500个)": "(限量500个)",
"1. 先成为活跃普通会员并推荐MINT 20枚NFT": "1. 先成为活跃普通会员并推荐MINT 20枚NFT",
"2. 需支付100USDT起升级费用成为社长(第二个起递增10%)": "2. 需支付100USDT起升级费用成为社长(第二个起递增10%)",
"3. 享普通会员权益+0.5%空投代币平分(所有社长)": "3. 享普通会员权益+0.5%空投代币平分(所有社长)",
"4. 享社长升级费用50%平分(扣除2USDT GAS费后)": "4. 享社长升级费用50%平分(扣除2USDT GAS费后)",
"5. 从推荐MINT 21枚开始直推的铸造费用归社长所有": "5. 从推荐MINT 21枚开始直推的铸造费用归社长所有",
"基金会社长": "基金会社长",
"(限量20个)": "(限量20个)",
"1. 先成为社长并团队中有20个社长": "1. 先成为社长并团队中有20个社长",
"2. 享社长权益+0.5%空投代币平分(所有基金会社长)": "2. 享社长权益+0.5%空投代币平分(所有基金会社长)",
"3. 另外基金会社长参与所有项目分成": "3. 另外基金会社长参与所有项目分成",
"发放记录": "发放记录",
"升级费平分": "升级费平分",
"直推>20NFT": "直推>20NFT",
"空投记录": "空投记录",
"所有": "所有",
"NFT空投": "NFT空投",
"社长空投": "社长空投",
"直推空投": "直推空投",
"奖励类型": "奖励类型",
"NFT控投": "NFT控投",
"发放时间": "发放时间",
"发放数量": "发放数量",
"领取记录": "领取记录",
"领取时间": "领取时间",
"领取数量": "领取数量",
"领取状态": "领取状态",
"确认中": "确认中",
"领取成功": "领取成功",
"交易取消": "交易取消",
"地址": "地址",
"级别": "级别",
"直推NFT": "直推NFT",
"非活跃普通": "非活跃普通",
"活跃普通": "活跃普通",
"复制成功": "复制成功",
"领取成功,前往钱包查看": "领取成功,前往钱包查看",
"链接钱包中...": "链接钱包中...",
"无": "无",
"服务器错误": "服务器错误",
"余额不足": "余额不足",
"MINT成功返回首页查看": "MINT成功返回首页查看",
"正在授权USDT": "正在授权USDT",
"购买中": "购买中",
"领取中": "领取中",
"MINT Nft 获取邀请链接": "MINT Nft 获取邀请链接",
"链接钱包获取邀请链接": "链接钱包获取邀请链接",
"正在获取已授权金额": "正在获取已授权金额",
"升级成功,返回首页查看": "升级成功,返回首页查看",
"无级别提升": "无级别提升",
"没有更多数据了": "没有更多数据了",
"升级中": "升级中",
"无等级": "无等级"
}

View File

@ -1,107 +0,0 @@
{
"AppName": "Der Rote Teufel",
"红魔NFT": "Der Rote Teufel NFT",
"铸造 NFT": "NFT Prägen",
"级别提升": "Stufenaufstieg",
"收益记录": "Ergebnisrekord",
"RMOB记录": "RMOB Aufzeichnung",
"直推列表": "Direktempfehlungsliste",
"返回": "Zurück",
"选择语言": "Sprache wählen",
"确定": "Bestätigen",
"取消": "Abbrechen",
"普通非活跃": "Inaktiv Mitglied",
"升级": "Upgrade",
"链接钱包": "Wallet verbinden",
"邀请铸造": "Prägen einladen",
"团队社长": "Teamleiter",
"邀请空投": "Airdrop einladen",
"收益": "Einkommen",
"Min结束后按照规则进行空投。": "Airdrop nach dem Ende des Min gemäß den Regeln.",
"铸造 NFT 获得代币空投": "Token-Airdrop durch NFT-Prägung erhalten",
"总收益= 已领取 + 待领取": "Gesamtumsatz = Empfang + Abzuholen",
"邀请": "Einladen",
"邀请链接": "Einladungslink",
"普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。": "Normale Mitglieder erhalten für jede Einladung zur Prägung eines NFTs einen Airdrop-Bonus; 20 empfohlene Prägungen führen zum Aufstieg zum Vorsitzenden; 20 Vorsitzende im Team ermöglichen den Aufstieg zum Fundmanager; je mehr Einladungen, desto höher das Level und die Belohnungen.",
"数据披露": "Datenoffenlegung",
"资金池": "Liquiditätspool",
"社长席位": "Vorsitzenderplatz",
"基金会社长席位": "Fundmanagerplatz",
"待领取": "Ausstehend",
"领取": "Erhalten",
"钱包未链接,无法向您显示 NFT": "Wallet nicht verbunden, kann NFT nicht anzeigen",
"邀请列表": "Einladungsliste",
"NFT总量": "Gesamtzahl der NFTs:",
"MINT余量": "Verbleibende MINT:",
"当前MINT价格": "Aktueller MINT-Preis:",
"价格说明:": "Preiserklärung:",
"{{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:",
"升级费用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",
"(限量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)",
"4. 活跃普通会员根据推荐MINT数量额外有空投权益。": "4. Aktive Mitglieder erhalten zusätzliche Airdrop-Rechte basierend auf der Anzahl der empfohlenen Prägungen.",
"(限量500个)": "(Begrenzt auf 500 Stück)",
"1. 先成为活跃普通会员并推荐MINT 20枚NFT": "1. Werde zuerst aktives Mitglied und empfehle die Prägung von 20 NFTs",
"2. 需支付100USDT起升级费用成为社长(第二个起递增10%)": "2. Zahle ab 100USDT für das Upgrade zum Vorsitzenden (ab dem zweiten um 10% steigend)",
"3. 享普通会员权益+0.5%空投代币平分(所有社长)": "3. Erhalte die Rechte eines normalen Mitglieds + 0,5% Token-Airdrop gleichmäßig verteilt (alle Vorsitzenden)",
"4. 享社长升级费用50%平分(扣除2USDT GAS费后)": "4. Erhalte 50% der Upgrade-Kosten des Vorsitzenden gleichmäßig verteilt (abzüglich 2USDT Gas-Gebühr)",
"5. 从推荐MINT 21枚开始直推的铸造费用归社长所有": "5. Ab 21 empfohlenen Prägungen gehen die Prägekosten direkt an den Vorsitzenden",
"基金会社长": "Fundmanager",
"(限量20个)": "(Begrenzt auf 20 Stück)",
"1. 先成为社长并团队中有20个社长": "1. Werde zuerst Vorsitzender und habe 20 Vorsitzende im Team",
"2. 享社长权益+0.5%空投代币平分(所有基金会社长)": "2. Erhalte die Rechte eines Vorsitzenden + 0,5% Token-Airdrop gleichmäßig verteilt (alle Fundmanager)",
"3. 另外基金会社长参与所有项目分成": "3. Fundmanager nehmen zusätzlich an allen Projektgewinnen teil",
"发放记录": "Verteilungsprotokoll",
"升级费平分": "Upgrade-Gebührenverteilung",
"直推>20NFT": "Direktempfehlung > 20 NFT",
"空投记录": "Airdrop-Protokoll",
"所有": "Alle",
"NFT空投": "NFT-Airdrop",
"社长空投": "Vorsitzender-Airdrop",
"直推空投": "Direktempfehlung Airdrop",
"奖励类型": "Belohnungstyp",
"NFT控投": "NFT-Besitz",
"发放时间": "Verteilungszeit",
"发放数量": "Verteilungsmenge",
"领取记录": "Empfangsprotokoll",
"领取时间": "Empfangszeit",
"领取数量": "Empfangsmenge",
"领取状态": "Empfangsstatus",
"确认中": "Bestätigung läuft",
"领取成功": "Erfolgreich empfangen",
"交易取消": "Transaktion abgebrochen",
"地址": "Adresse",
"级别": "Stufe",
"直推NFT": "Direktempfehlung NFT",
"非活跃普通": "Inaktiv allgemein",
"活跃普通": "Aktiv allgemein",
"复制成功": "Erfolgreich kopiert",
"领取成功,前往钱包查看": "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",
"升级中": "Wird aktualisiert",
"无等级": "Keine Bewertung"
}

View File

@ -1,11 +1,11 @@
{ {
"AppName": "Red Devils", "AppName": "Edge AI Node",
"红魔NFT": "Red Devils NFT", "YOTTA Edge AI Node": "YOTTA Edge AI Node",
"铸造 NFT": "Mint NFT", "Buy Node": "Buy Node",
"级别提升": "Level Up", "级别提升": "Level Up",
"收益记录": "Revenue Record", "收益记录": "Revenue Record",
"RMOB记录": "RMOB Records", "RMOB记录": "RMOB Records",
"直推列表": "Direct Referral List", "Invitation List": "Invitation List",
"返回": "Back", "返回": "Back",
"选择语言": "Select Language", "选择语言": "Select Language",
"确定": "Confirm", "确定": "Confirm",
@ -13,23 +13,23 @@
"普通非活跃": "Inactive Member", "普通非活跃": "Inactive Member",
"升级": "Upgrade", "升级": "Upgrade",
"链接钱包": "Connect Wallet", "链接钱包": "Connect Wallet",
"邀请铸造": "Invite to Mint", "Direct Node": "Direct Node",
"团队社长": "Team Leader", "Team Node": "Team Node",
"邀请空投": "Invite Airdrop", "Push income": "Push income",
"收益": "Income", "收益": "Income",
"Min结束后按照规则进行空投。": "Airdrop will follow the rules after Min ends.", "Start mining after node subscription ends.": "Start mining after node subscription ends.",
"铸造 NFT 获得代币空投": "Mint NFT for Token Airdrop", "Buy Edge AI Node.": "Buy Edge AI Node.",
"总收益= 已领取 + 待领取": "Total Revenue = Claimed + Pending", "总收益= 已领取 + 待领取": "Total Revenue = Claimed + Pending",
"邀请": "Invite", "邀请": "Invite",
"邀请链接": "Invite Link", "邀请链接": "Invite Link",
"普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。": "Regular members get an airdrop for each NFT minted through their invite; recommending 20 NFTs upgrades to Leader; having 20 Leaders in the team upgrades to Foundation Leader; more invites lead to higher levels and more benefits.", "Invite your friends to become YOTTA nodes and you will get a 20% rebate.": "Invite your friends to become YOTTA nodes and you will get a 20% rebate.",
"数据披露": "Data Disclosure", "数据披露": "Data Disclosure",
"资金池": "Fund Pool", "资金池": "Fund Pool",
"社长席位": "Leader Seats", "社长席位": "Leader Seats",
"基金会社长席位": "Foundation Leader Seats", "基金会社长席位": "Foundation Leader Seats",
"待领取": "Pending", "待领取": "Pending",
"领取": "Claim", "领取": "Claim",
"钱包未链接,无法向您显示 NFT": "Wallet not linked, unable to display NFT", "The wallet is not linked and cannot show you the Node.": "The wallet is not linked and cannot show you the Node.",
"邀请列表": "Invite List", "邀请列表": "Invite List",
"NFT总量": "Total NFTs:", "NFT总量": "Total NFTs:",
"MINT余量": "Remaining MINT:", "MINT余量": "Remaining MINT:",
@ -92,16 +92,17 @@
"无": "None", "无": "None",
"服务器错误": "Server Error", "服务器错误": "Server Error",
"余额不足": "Insufficient balance", "余额不足": "Insufficient balance",
"MINT成功返回首页查看": "MINT succeeded, return to the homepage to check.", "Buy successful. Please return to the homepage to view.": "Buy successful. Please return to the homepage to view.",
"正在授权USDT": "Authorizing USDT", "正在授权USDT": "Approving USDT",
"购买中": "Purchasing", "购买中": "Purchasing",
"领取中": "Processing", "领取中": "Processing",
"MINT Nft 获取邀请链接": "Mint NFT to get invitation link", "Buy Edge AI Node to get invitation link": "Buy Edge AI Node to get invitation link",
"链接钱包获取邀请链接": "Link your wallet to get the invitation link.", "Link wallet to get invitation link": "Link wallet to get invitation link",
"正在获取已授权金额": "Currently retrieving authorized amount.", "正在获取已授权金额": "Currently retrieving authorized amount.",
"升级成功,返回首页查看": "Upgrade successful, return to homepage to view.", "升级成功,返回首页查看": "Upgrade successful, return to homepage to view.",
"无级别提升": "Promotion without rank", "无级别提升": "Promotion without rank",
"没有更多数据了": "No more data", "没有更多数据了": "No more data",
"升级中": "Upgrading", "升级中": "Upgrading",
"无等级": "No Grade" "无等级": "No Grade",
"invalid invitation link": "invalid invitation link"
} }

View File

@ -1,107 +0,0 @@
{
"AppName": "紅魔",
"红魔NFT": "紅魔NFT",
"铸造 NFT": "NFT 鋳造",
"级别提升": "レベルアップ",
"收益记录": "収益実績",
"RMOB记录": "RMOB記録",
"直推列表": "ダイレクト推薦リスト",
"返回": "戻る",
"选择语言": "言語選択",
"确定": "確定",
"取消": "キャンセル",
"普通非活跃": "非活躍メンバー",
"升级": "アップグレード",
"链接钱包": "ウォレットを接続",
"邀请铸造": "鋳造を招待",
"团队社长": "チームリーダー",
"邀请空投": "エアドロップ招待",
"收益": "所得",
"Min结束后按照规则进行空投。": "Min終了後、ルールに従ってエアドロップを行います。",
"铸造 NFT 获得代币空投": "NFTを鋳造してトークンエアドロップを獲得",
"总收益= 已领取 + 待领取": "総収入 = 受領済み + 回収対象",
"邀请": "招待",
"邀请链接": "招待リンク",
"普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。": "一般会員はNFT鋳造の招待ごとにエアドロップを獲得20個のNFTを鋳造することでリーダーに昇格チーム内に20人のリーダーがいるとファンドリーダーに昇格招待が多いほどレベルが上がり、特典が増える。",
"数据披露": "データ開示",
"资金池": "資金プール",
"社长席位": "リーダー席",
"基金会社长席位": "ファンドリーダー席",
"待领取": "未受領",
"领取": "受領",
"钱包未链接,无法向您显示 NFT": "ウォレットが接続されていないため、NFTを表示できません",
"邀请列表": "招待リスト",
"NFT总量": "NFT総量",
"MINT余量": "残りのMINT",
"当前MINT价格": "現在のMINT価格",
"价格说明:": "価格説明:",
"{{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の承認",
"当前级别": "現在のレベル",
"普通活跃": "活躍メンバー",
"提升级别": "レベルアップ",
"社长": "リーダー",
"当前升级价格:": "現在のアップグレード価格:",
"升级费用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、以降も同様。",
"升级条件": "アップグレード条件",
"普通会员": "一般会員",
"(限量xxx个)": "(限定 {{value}} 個)",
"1. MINT一枚NFT成为非活跃普通会员": "1. NFTを1枚鋳造して非活躍メンバーになる",
"2. MINT NFT并推荐MINT成为活跃普通会员": "2. NFTを鋳造し、他者に鋳造を推奨して活躍メンバーになる",
"3. 享1%代币空投平分(所有普通会员)": "3. 一般会員は1%のトークンエアドロップを均等に分配",
"4. 活跃普通会员根据推荐MINT数量额外有空投权益。": "4. 活躍メンバーは推奨MINT数に応じて追加エアドロップ権を持つ。",
"(限量500个)": "(限定500個)",
"1. 先成为活跃普通会员并推荐MINT 20枚NFT": "1. まず活躍メンバーになり、20枚のNFTを推奨して鋳造",
"2. 需支付100USDT起升级费用成为社长(第二个起递增10%)": "2. 100USDTからアップグレード費用を支払いリーダーになる2人目以降は10%増加)",
"3. 享普通会员权益+0.5%空投代币平分(所有社长)": "3. 一般会員の権利に加え、リーダーは0.5%のトークンエアドロップを均等に分配",
"4. 享社长升级费用50%平分(扣除2USDT GAS费后)": "4. リーダーのアップグレード費用の50%を分配2USDTのガス代を除く",
"5. 从推荐MINT 21枚开始直推的铸造费用归社长所有": "5. 推奨MINTが21枚以上になると、鋳造費用はリーダーのものとなる",
"基金会社长": "ファンドリーダー",
"(限量20个)": "(限定20個)",
"1. 先成为社长并团队中有20个社长": "1. まずリーダーになり、チームに20人のリーダーがいる",
"2. 享社长权益+0.5%空投代币平分(所有基金会社长)": "2. リーダーの権利に加え、ファンドリーダーは0.5%のトークンエアドロップを均等に分配",
"3. 另外基金会社长参与所有项目分成": "3. さらにファンドリーダーはすべてのプロジェクトの利益分配に参加",
"发放记录": "配布記録",
"升级费平分": "アップグレード費用分配",
"直推>20NFT": "ダイレクト推薦>20NFT",
"空投记录": "エアドロップ記録",
"所有": "すべて",
"NFT空投": "NFTエアドロップ",
"社长空投": "リーダーエアドロップ",
"直推空投": "ダイレクト推薦エアドロップ",
"奖励类型": "報酬タイプ",
"NFT控投": "NFT保有",
"发放时间": "配布時間",
"发放数量": "配布数量",
"领取记录": "受領記録",
"领取时间": "受領時間",
"领取数量": "受領数量",
"领取状态": "受領状態",
"确认中": "確認中",
"领取成功": "受領成功",
"交易取消": "取引キャンセル",
"地址": "アドレス",
"级别": "レベル",
"直推NFT": "ダイレクト推薦NFT",
"非活跃普通": "非活躍一般",
"活跃普通": "活躍一般",
"复制成功": "コピーが成功しました",
"领取成功,前往钱包查看": "受け取りが成功しました。ウォレットで確認してください。",
"链接钱包中...": "ウォレットをリンク中...",
"无": "無",
"服务器错误": "サーバーエラー",
"余额不足": "ざんだかふそく",
"MINT成功返回首页查看": "MINTが成功しました。ホームページに戻って確認してください",
"正在授权USDT": "USDTの認証中",
"购买中": "購入中 (こうにゅうちゅう)",
"领取中": "しょりちゅう",
"MINT Nft 获取邀请链接": "MINT Nft 招待リンクを取得する",
"链接钱包获取邀请链接": "ウォレットをリンクして招待リンクを取得します。",
"正在获取已授权金额": "承認済み金額を取得中です",
"升级成功,返回首页查看": "アップグレードが成功しました。ホームページに戻って確認してください。",
"无级别提升": "階級なしの昇進",
"没有更多数据了": "これ以上のデータはありません",
"升级中": "アップグレード中",
"无等级": "グレードなし"
}

View File

@ -1,107 +0,0 @@
{
"AppName": "紅魔",
"红魔NFT": "紅魔NFT",
"铸造 NFT": "鑄造 NFT",
"级别提升": "級別提升",
"收益记录": "收益記錄",
"RMOB记录": "RMOB記錄",
"直推列表": "直推列表",
"返回": "返回",
"选择语言": "選擇語言",
"确定": "確定",
"取消": "取消",
"普通非活跃": "普通非活躍",
"升级": "升級",
"链接钱包": "鏈接錢包",
"邀请铸造": "邀請鑄造",
"团队社长": "團隊社長",
"邀请空投": "邀請空投",
"收益": "收益",
"Min结束后按照规则进行空投。": "Min結束後按照規則進行空投。",
"铸造 NFT 获得代币空投": "鑄造 NFT 獲得代幣空投",
"总收益= 已领取 + 待领取": "總收益 = 已領取 + 待領取",
"邀请": "邀請",
"邀请链接": "邀請鏈接",
"普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。": "普通會員每邀請鑄造一個NFT可獲得一份空投福利推薦鑄造20個NFT的可升級為會長團隊中擁有20位會長可升級為基金會社長邀請越多級別越高福利越多。",
"数据披露": "數據披露",
"资金池": "資金池",
"社长席位": "社長席位",
"基金会社长席位": "基金會社長席位",
"待领取": "待領取",
"领取": "領取",
"钱包未链接,无法向您显示 NFT": "錢包未鏈接,無法向您顯示 NFT",
"邀请列表": "邀請列表",
"NFT总量": "NFT總量",
"MINT余量": "MINT餘量",
"当前MINT价格": "當前MINT價格",
"价格说明:": "價格說明:",
"{{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",
"当前级别": "當前級別",
"普通活跃": "普通活躍",
"提升级别": "提升級別",
"社长": "社長",
"当前升级价格:": "當前升級價格:",
"升级费用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以此類推。",
"升级条件": "升級條件",
"普通会员": "普通會員",
"(限量xxx个)": "(限量 {{value}} 個)",
"1. MINT一枚NFT成为非活跃普通会员": "1. MINT一枚NFT成為非活躍普通會員",
"2. MINT NFT并推荐MINT成为活跃普通会员": "2. MINT NFT並推薦MINT成為活躍普通會員",
"3. 享1%代币空投平分(所有普通会员)": "3. 享1%代幣空投平分(所有普通會員)",
"4. 活跃普通会员根据推荐MINT数量额外有空投权益。": "4. 活躍普通會員根據推薦MINT數量額外有空投權益。",
"(限量500个)": "(限量500個)",
"1. 先成为活跃普通会员并推荐MINT 20枚NFT": "1. 先成為活躍普通會員並推薦MINT 20枚NFT",
"2. 需支付100USDT起升级费用成为社长(第二个起递增10%)": "2. 需支付100USDT起升級費用成為社長(第二個起遞增10%)",
"3. 享普通会员权益+0.5%空投代币平分(所有社长)": "3. 享普通會員權益+0.5%空投代幣平分(所有社長)",
"4. 享社长升级费用50%平分(扣除2USDT GAS费后)": "4. 享社長升級費用50%平分(扣除2USDT GAS費後)",
"5. 从推荐MINT 21枚开始直推的铸造费用归社长所有": "5. 從推薦MINT 21枚開始直推的鑄造費用歸社長所有",
"基金会社长": "基金會社長",
"(限量20个)": "(限量20個)",
"1. 先成为社长并团队中有20个社长": "1. 先成為社長並團隊中有20個社長",
"2. 享社长权益+0.5%空投代币平分(所有基金会社长)": "2. 享社長權益+0.5%空投代幣平分(所有基金會社長)",
"3. 另外基金会社长参与所有项目分成": "3. 另外基金會社長參與所有項目分成",
"发放记录": "發放記錄",
"升级费平分": "升級費平分",
"直推>20NFT": "直推>20NFT",
"空投记录": "空投記錄",
"所有": "所有",
"NFT空投": "NFT空投",
"社长空投": "社長空投",
"直推空投": "直推空投",
"奖励类型": "獎勵類型",
"NFT控投": "NFT控投",
"发放时间": "發放時間",
"发放数量": "發放數量",
"领取记录": "領取記錄",
"领取时间": "領取時間",
"领取数量": "領取數量",
"领取状态": "領取狀態",
"确认中": "確認中",
"领取成功": "領取成功",
"交易取消": "交易取消",
"地址": "地址",
"级别": "級別",
"直推NFT": "直推NFT",
"非活跃普通": "非活躍普通",
"活跃普通": "活躍普通",
"复制成功": "複製成功",
"领取成功,前往钱包查看": "領取成功,前往錢包查看",
"链接钱包中...": "連結錢包中...",
"无": "無",
"服务器错误": "伺服器錯誤",
"余额不足": "餘額不足",
"MINT成功返回首页查看": "MINT成功返回首頁查看",
"正在授权USDT": "正在授權USDT",
"购买中": "購買中",
"领取中": "處理中",
"MINT Nft 获取邀请链接": "MINT Nft 獲取邀請鏈接",
"链接钱包获取邀请链接": "連結錢包以獲取邀請鏈接。",
"正在获取已授权金额": "正在取得已授權金額",
"升级成功,返回首页查看": "升級成功,返回首頁查看。",
"无级别提升": "無級別提升",
"没有更多数据了": "沒有更多數據了",
"升级中": "升級中",
"无等级": "無等級"
}

View File

@ -1,9 +0,0 @@
.AirDropRecord {
.recordsList {
display: flex;
flex-direction: column;
gap: 10px;
padding: 0 15px;
margin-top: 20px;
}
}

View File

@ -1,97 +0,0 @@
/*
* @LastEditors: John
* @Date: 2024-06-19 10:43:03
* @LastEditTime: 2024-06-19 14:25:41
* @Author: John
*/
import RecordsItem from "@/components/RecordsItem";
import CapsuleTabs from "antd-mobile/es/components/capsule-tabs";
import Tabs from "antd-mobile/es/components/tabs";
import classes from "./AirDropRecord.module.css";
import { useTranslation } from "react-i18next";
export default function () {
const { t } = useTranslation();
return (
<>
<Tabs className={classes.AirDropRecord}>
<Tabs.Tab title={t("空投记录")} key="1">
<CapsuleTabs>
<CapsuleTabs.Tab title={t("所有")} key="1" />
<CapsuleTabs.Tab title={t("NFT空投")} key="2" />
<CapsuleTabs.Tab title={t("社长空投")} key="3" />
<CapsuleTabs.Tab title={t("基金会社长")} key="4" />
<CapsuleTabs.Tab title={t("直推空投")} key="5" />
</CapsuleTabs>
<ul className={classes.recordsList}>
{Array.from({ length: 20 }).map((v, i) => (
<li key={i}>
<RecordsItem
itemList={[
{
title: t("奖励类型"),
value: t("NFT控投"),
},
{ title: t("发放时间"), value: "2024-06-01 12:23:45" },
{ title: t("发放数量"), value: "2.00 RMOB" },
]}
/>
</li>
))}
</ul>
</Tabs.Tab>
<Tabs.Tab title={t("领取记录")} key="2">
<ul className={classes.recordsList}>
<li>
<RecordsItem
itemList={[
{
title: t("领取时间"),
value: "2024-06-01 12:23:45",
},
{ title: t("领取数量"), value: "2.00 RMOB" },
{
title: t("领取状态"),
value: t("确认中"),
valueColor: "#FC872B",
},
]}
/>
</li>
<li>
<RecordsItem
itemList={[
{
title: t("领取时间"),
value: "2024-06-01 12:23:45",
},
{ title: t("领取数量"), value: "2.00 USDT" },
{
title: t("领取状态"),
value: t("领取成功"),
valueColor: "#38C979",
},
]}
/>
</li>
<li>
<RecordsItem
itemList={[
{
title: t("领取时间"),
value: "2024-06-01 12:23:45",
},
{ title: t("领取数量"), value: "2.00 USDT" },
{
title: t("领取状态"),
value: t("交易取消"),
valueColor: "#C94738",
},
]}
/>
</li>
</ul>
</Tabs.Tab>
</Tabs>
</>
);
}

View File

@ -1,9 +0,0 @@
.AssetRecord {
.recordsList {
display: flex;
flex-direction: column;
gap: 10px;
padding: 0 15px;
margin-top: 20px;
}
}

View File

@ -1,212 +0,0 @@
/*
* @LastEditors: John
* @Date: 2024-06-18 17:57:13
* @LastEditTime: 2024-06-25 16:17:20
* @Author: John
*/
import Tabs from "antd-mobile/es/components/tabs";
import classes from "./AssetRecord.module.css";
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>(2);
const [issueRecords, setIssueRecords] = useState<IncomeRecord["records"]>([]);
const [receiveRecord, setReceiveRecord] = useState<IncomeRecord["records"]>(
[]
);
const conditions = useRef<IncomeRecordType>();
const pageNum = useRef<number>(0);
const hasMore = useRef<boolean>(true);
useEffect(() => {
return () => {};
}, []);
async function getRecord() {
return new Promise<void>(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 == 2
? { status: conditions.current }
: {}),
},
});
if (!data?.data.records) return;
if (data.data.records.length < pageSize) hasMore.current = false;
if (currentType.current == 2) {
setIssueRecords([...issueRecords, ...data?.data.records]);
} else {
setReceiveRecord([...receiveRecord, ...data?.data.records]);
}
reslove();
});
}
function resetPaging() {
if (currentType.current == 2) {
setIssueRecords([]);
} else if (currentType.current == 1) {
setReceiveRecord([]);
}
pageNum.current = 0;
hasMore.current = true;
}
return (
<>
<Tabs
className={cn(classes.AssetRecord)}
onChange={(key) => {
if (parseInt(key) == 1) {
currentType.current = 2;
resetPaging();
} else {
currentType.current = 1;
resetPaging();
}
}}
>
<Tabs.Tab className={classes.tab} title={t("发放记录")} key="1">
{coinName == CoinName.USDT && (
<CapsuleTabs
onChange={(key) => {
switch (key) {
case "1":
conditions.current = undefined;
break;
case "2":
conditions.current = 5;
break;
case "3":
conditions.current = 4;
break;
default:
break;
}
resetPaging();
}}
>
<CapsuleTabs.Tab title={t("所有")} key="1" />
<CapsuleTabs.Tab title={t("升级费平分")} key="2" />
<CapsuleTabs.Tab title={t("直推>20NFT")} key="3" />
</CapsuleTabs>
)}
{coinName == CoinName.RMOB && (
<CapsuleTabs
onChange={(key) => {
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();
}}
>
<CapsuleTabs.Tab title={t("所有")} key="1" />
<CapsuleTabs.Tab title={t("NFT空投")} key="2" />
<CapsuleTabs.Tab title={t("社长空投")} key="3" />
<CapsuleTabs.Tab title={t("基金会社长")} key="4" />
<CapsuleTabs.Tab title={t("直推空投")} key="5" />
</CapsuleTabs>
)}
<ul className={classes.recordsList}>
{issueRecords?.map((v, i) => (
<li key={i}>
<RecordsItem
itemList={[
{
title: t("奖励类型"),
value: v.extRemark,
},
{ title: t("发放时间"), value: v.createTime },
{ title: t("发放数量"), value: `${v.opValue} USDT` },
]}
/>
</li>
))}
{issueRecords?.length == 0 && <Empty />}
<InfiniteScroll loadMore={getRecord} hasMore={hasMore.current}>
<span>{t("没有更多数据了")}</span>
</InfiniteScroll>
</ul>
</Tabs.Tab>
<Tabs.Tab className={classes.tab} title={t("领取记录")} key="2">
<ul className={classes.recordsList}>
{receiveRecord?.map((v, i) => (
<li key={i}>
<RecordsItem
itemList={[
{
title: t("领取时间"),
value: v.createTime,
},
{ title: t("领取数量"), value: `${v.opValue} USDT` },
{
title: t("领取状态"),
...(v.type == 1 && {
value: t("领取成功"),
valueColor: "#38C979",
}),
...(v.type == 3 && {
value: t("确认中"),
valueColor: "#FC872B",
}),
...(v.type == 4 && {
value: t("交易取消"),
valueColor: "#C94738",
}),
},
]}
/>
</li>
))}
{receiveRecord?.length == 0 && <Empty />}
<InfiniteScroll loadMore={getRecord} hasMore={hasMore.current}>
<span>{t("没有更多数据了")}</span>
</InfiniteScroll>
</ul>
</Tabs.Tab>
</Tabs>
</>
);
}

View File

@ -16,8 +16,8 @@
background: #171719; background: #171719;
box-shadow: 0px 4px 10px 0px rgba(138, 29, 19, 0.3), box-shadow: 0px 4px 10px 0px rgba(45, 252, 252, 0.3),
inset 0px 0px 8px 0px #8a1d13; inset 0px 0px 8px 0px #2dfcfc;
padding: 14px 15px; padding: 14px 15px;
box-sizing: border-box; box-sizing: border-box;
@ -49,7 +49,7 @@
span { span {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -93,7 +93,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -117,7 +117,7 @@
border-radius: 10px; border-radius: 10px;
opacity: 1; opacity: 1;
background: #fc872b; background: #2dfcfc;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid; border: 1px solid;
@ -130,7 +130,7 @@
span { span {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -138,7 +138,7 @@
font-variation-settings: "opsz" auto; font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on; font-feature-settings: "kern" on;
color: #ffffff; color: #101010;
} }
} }
} }
@ -165,7 +165,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -176,12 +176,33 @@
color: #ffffff; color: #ffffff;
z-index: 0; z-index: 0;
display: flex;
align-items: center;
gap: 4px;
p {
/* 自动布局子元素 */
opacity: 1;
font-family: Space Grotesk;
font-size: 10px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 1;
}
} }
.userinfo_data_des { .userinfo_data_des {
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -209,7 +230,7 @@
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -224,9 +245,9 @@
border-radius: 10px; border-radius: 10px;
opacity: 1; opacity: 1;
background: #fc872b; background: #2dfcfc;
color: #ffffff; color: #101010;
} }
} }
} }
@ -257,7 +278,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -274,7 +295,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -282,7 +303,7 @@
font-variation-settings: "opsz" auto; font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on; font-feature-settings: "kern" on;
color: #f3be3c; color: #2dfcfc;
z-index: 0; z-index: 0;
@ -306,7 +327,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -331,7 +352,7 @@
span { span {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -365,7 +386,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -382,7 +403,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -399,7 +420,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -436,7 +457,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -474,7 +495,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -514,14 +535,14 @@
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 10px 42px; padding: 10px 0;
background: #fc872b; background: #2dfcfc;
z-index: 0; z-index: 0;
gap: 10px; gap: 10px;
box-sizing: border-box; box-sizing: border-box;
span { span {
/* 自动布局子元素 */ /* 自动布局子元素 */
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -530,21 +551,21 @@
font-variation-settings: "opsz" auto; font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on; font-feature-settings: "kern" on;
color: #ffffff; color: #101010;
z-index: 0; z-index: 0;
white-space: nowrap; white-space: nowrap;
} }
.nftToken_content_nft_mint_btn_icon { .nftToken_content_nft_mint_btn_icon {
width: 24px; width: 16px;
height: 24px; height: 16px;
} }
} }
> span { > span {
/* 自动布局子元素 */ /* 自动布局子元素 */
font-family: DM Sans; font-family: Space Grotesk;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -570,7 +591,7 @@
span { span {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -596,7 +617,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -612,7 +633,7 @@
&:nth-of-type(2) { &:nth-of-type(2) {
/* 自动布局子元素 */ /* 自动布局子元素 */
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -620,7 +641,7 @@
font-variation-settings: "opsz" auto; font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on; font-feature-settings: "kern" on;
color: #f3be3c; color: #2dfcfc;
z-index: 0; z-index: 0;
@ -651,7 +672,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 0.8; opacity: 0.8;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -668,7 +689,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -691,7 +712,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -722,7 +743,7 @@
margin-top: 24px; margin-top: 24px;
> span { > span {
/* 自动布局子元素 */ /* 自动布局子元素 */
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -758,7 +779,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -775,7 +796,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;

View File

@ -1,6 +1,6 @@
import classes from "./Home.module.css"; import classes from "./Home.module.css";
import useUserStore from "@/store/User"; import useUserStore from "@/store/User";
import { cn, copyText, shortenString } from "@/utils"; import { cn, copyText, getLevelName, shortenString } from "@/utils";
import { useWeb3Modal } from "@web3modal/wagmi/react"; import { useWeb3Modal } from "@web3modal/wagmi/react";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -11,15 +11,19 @@ import IconFont from "@/components/iconfont";
import { BaseError, useAccount } from "wagmi"; import { BaseError, useAccount } from "wagmi";
import { config } from "@/components/WalletProvider"; import { config } from "@/components/WalletProvider";
import { createSearchParams, useNavigate } from "react-router-dom"; import { createSearchParams, useNavigate } from "react-router-dom";
import { Button, Dialog, Empty, Toast } from "antd-mobile"; import { Button, Dialog, Empty, PullToRefresh, Toast } from "antd-mobile";
import { loginOut } from "@/utils/wallet"; import { loginOut } from "@/utils/wallet";
import { api_claim_income, api_get_homepage_user_data } from "@/server/api"; import {
api_claim_income,
api_get_homepage_user_data,
api_query_user_invitation_code,
} from "@/server/api";
import { UserHomeData } from "@/server/module"; import { UserHomeData } from "@/server/module";
import { UrlQueryParamsKey } from "@/constants"; import { UrlQueryParamsKey } from "@/constants";
import { receiveByContract } from "@/contract/utils";
import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus"; import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus";
import { ToastHandler } from "antd-mobile/es/components/toast"; import { ToastHandler } from "antd-mobile/es/components/toast";
import { disconnect, getAccount } from "@wagmi/core"; import { disconnect, getAccount } from "@wagmi/core";
import { PullStatus } from "antd-mobile/es/components/pull-to-refresh";
export default function () { export default function () {
const { Token, UpdateToken } = useUserStore(); const { Token, UpdateToken } = useUserStore();
const { open } = useWeb3Modal(); const { open } = useWeb3Modal();
@ -29,13 +33,12 @@ export default function () {
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const navigate = useNavigate(); const navigate = useNavigate();
const [userData, setUserData] = useState<UserHomeData>(); const [userData, setUserData] = useState<UserHomeData>();
const [inviteCode, setInviteCode] = useState("");
const userInviteLink = useMemo( const userInviteLink = useMemo(
() => () =>
`${import.meta.env.VITE_BASE_URL}#/?${UrlQueryParamsKey.INVITE_CODE}=${ `${location.origin}/#/?${UrlQueryParamsKey.INVITE_CODE}=${inviteCode}`,
userData?.invitationCode || "" [inviteCode]
}`,
[userData]
); );
const receiveLoadingToast = useRef<ToastHandler>(); const receiveLoadingToast = useRef<ToastHandler>();
const { const {
@ -44,8 +47,18 @@ export default function () {
stopPollingCheckBuyStatus, stopPollingCheckBuyStatus,
} = usePollingCheckBuyStatus("NORMAL"); } = usePollingCheckBuyStatus("NORMAL");
const statusRecord: Record<PullStatus, string> = {
pulling: "Pull down to refresh",
canRelease: "Release to refresh immediately",
refreshing: "Loading...",
complete: "Refresh complete",
};
useEffect(() => { useEffect(() => {
getHomeData(); if (Token) {
getHomeData();
getInviteCode();
}
return () => {}; return () => {};
}, [Token]); }, [Token]);
@ -67,6 +80,11 @@ export default function () {
setUserData(data?.data); setUserData(data?.data);
} }
async function getInviteCode() {
const { data } = await api_query_user_invitation_code().send({});
setInviteCode(data?.data.invitationCode || "");
}
useEffect(() => { useEffect(() => {
console.log("user token:", Token); console.log("user token:", Token);
@ -75,389 +93,222 @@ export default function () {
return ( return (
<> <>
<div className={cn(classes.Home, classes.container)}> <PullToRefresh
<div className={classes.userinfo}> onRefresh={async () => {
<div className={classes.userinfo_top}> await getHomeData();
<img className={classes.userinfo_top_left} src={logo} alt="" /> }}
renderText={(status) => {
return <div>{statusRecord[status]}</div>;
}}
disabled={!Token}
>
<div className={cn(classes.Home, classes.container)}>
<div className={classes.userinfo}>
<div className={classes.userinfo_top}>
<img className={classes.userinfo_top_left} src={logo} alt="" />
{address ? ( {address ? (
<div className={classes.userinfo_top_right}> <div className={classes.userinfo_top_right}>
<div className={classes.userinfo_top_right_wallet}> <div className={classes.userinfo_top_right_wallet}>
<span>{shortenString(address, 6, 4)}</span> <span>{shortenString(address, 6, 4)}</span>
<IconFont <IconFont
onClick={async () => { onClick={async () => {
const { connector } = getAccount(config); loginOut();
await disconnect(config, { connector }); setUserData(undefined);
loginOut(); }}
name="tuichu"
className={classes.userinfo_top_right_wallet_disconnect}
color={"#fff"}
/>
</div>
<div className={classes.userinfo_top_right_btns}>
{userData && (
<>
<div className={classes.userinfo_top_right_btns_item}>
<span>{getLevelName(userData.level)}</span>
</div>
</>
)}
</div>
</div>
) : (
<>
<div
className={classes.userinfo_top_right_connect}
onClick={() => {
open();
}} }}
name="tuichu" >
className={classes.userinfo_top_right_wallet_disconnect} <span>{t("链接钱包")}</span>
color={"#fff"} </div>
/> </>
</div> )}
<div className={classes.userinfo_top_right_btns}> </div>
{userData && ( <ul className={classes.userinfo_data}>
<> <li>
<div className={classes.userinfo_top_right_btns_item}> <span className={classes.userinfo_data_num}>
{userData.level == 0 && ( {userData?.directPushNode || 0}
<> </span>
<IconFont <span className={classes.userinfo_data_des}>
name="tongdun" {t("Direct Node")}
className={classes.userinfo_top_right_btns_icon} </span>
/> </li>
<span>{t("无等级")}</span> <li>
</> <span className={classes.userinfo_data_num}>
)} {userData?.teamNode || 0}
{userData.level == 1 && ( </span>
<> <span className={classes.userinfo_data_des}>
<IconFont {t("Team Node")}
name="jindun" </span>
className={classes.userinfo_top_right_btns_icon} </li>
/> <li>
{userData.active === 0 && ( <span className={classes.userinfo_data_num}>
<span>{t("普通非活跃")}</span> {userData?.revenueUsdt || 0}
)} <p>USDT</p>
{userData.active === 1 && ( </span>
<span>{t("普通活跃")}</span> <span className={classes.userinfo_data_des}>
)} {t("Push income")}
</> </span>
)} </li>
{userData.level == 2 && ( </ul>
<>
<IconFont
name="xingdun"
className={classes.userinfo_top_right_btns_icon}
/>
<span>{t("社长")}</span>
</>
)}
{userData.level == 3 && (
<>
<IconFont
name="guanjun"
className={classes.userinfo_top_right_btns_icon}
/>
<span>{t("基金会社长")}</span>
</>
)}
</div>
<div
className={classes.userinfo_top_right_btns_item}
onClick={() => {
navigate("/levelup");
}}
>
<span>{t("升级")}</span>
<IconFont
name="chevronsrightshuangyoujiantou"
className={classes.userinfo_top_right_btns_icon}
color={"#fff"}
/>
</div>
</>
)}
</div>
</div>
) : (
<>
<div
className={classes.userinfo_top_right_connect}
onClick={() => {
open();
}}
>
<span>{t("链接钱包")}</span>
</div>
</>
)}
</div> </div>
<ul className={classes.userinfo_data}>
<li>
<span className={classes.userinfo_data_num}>
{userData?.mintNumber || 0}
</span>
<span className={classes.userinfo_data_des}>{t("邀请铸造")}</span>
</li>
<li>
<span className={classes.userinfo_data_num}>
{userData?.presidentNumber || 0}
</span>
<span className={classes.userinfo_data_des}>{t("团队社长")}</span>
</li>
<li>
<span className={classes.userinfo_data_num}>
{userData?.airdropNumber || 0}
</span>
<span className={classes.userinfo_data_des}>{t("邀请空投")}</span>
</li>
</ul>
</div>
<div className={classes.nftToken}> <div className={classes.nftToken}>
<ul className={classes.nftToken_tab}> <ul className={classes.nftToken_tab}>
<li <li
className={tabIndex == 0 ? classes.nftToken_tab_active : ""} className={tabIndex == 0 ? classes.nftToken_tab_active : ""}
onClick={() => setTabIndex(0)} onClick={() => setTabIndex(0)}
> >
NFT NODE
</li> </li>
<li </ul>
className={tabIndex == 1 ? classes.nftToken_tab_active : ""}
onClick={() => setTabIndex(1)}
>
{t("收益")}
</li>
</ul>
<div className={classes.nftToken_content}> <div className={classes.nftToken_content}>
{tabIndex == 0 && ( {tabIndex == 0 && (
<> <>
{address ? ( {address ? (
<> <>
{userData?.nftId ? ( {userData?.nodeNumber && userData?.nodeNumber > 0 ? (
<div className={classes.nftToken_content_nft}> <div className={classes.nftToken_content_nft}>
<div className={classes.nftToken_content_nft_top}> <div className={classes.nftToken_content_nft_top}>
<span># {userData?.nftId}</span> <span>You own {userData.nodeNumber} nodes</span>
<span <span
onClick={() => {
navigate("/mint");
}}
>
{t("Buy Node")}
<IconFont
name="chevronsrightshuangyoujiantou"
color={"#2DFCFC"}
/>
</span>
</div>
<img
className={classes.nftToken_content_nft_img}
src={nftBg}
alt=""
/>
<span className={classes.nftToken_content_nft_des}>
{t("Start mining after node subscription ends.")}
</span>
</div>
) : (
<div className={classes.nftToken_content_nft_mint}>
<div
className={classes.nftToken_content_nft_mint_btn}
onClick={() => { onClick={() => {
navigate("/mint"); navigate("/mint");
}} }}
> >
{t("铸造 NFT")} <span>{t("Buy Node")}</span>
<IconFont <IconFont
className={
classes.nftToken_content_nft_mint_btn_icon
}
name="chevronsrightshuangyoujiantou" name="chevronsrightshuangyoujiantou"
color={"#F3BE3C"} color={"#101010"}
/> />
</span> </div>
<span>{t("Buy Edge AI Node.")}</span>
</div> </div>
<img )}
className={classes.nftToken_content_nft_img} </>
src={nftBg} ) : (
alt="" <>
/> <div className={classes.nftToken_content_userDisconnect}>
<span className={classes.nftToken_content_nft_des}> <span>
{t("Min结束后按照规则进行空投。")} {t(
"The wallet is not linked and cannot show you the Node."
)}
</span> </span>
</div> </div>
) : ( </>
<div className={classes.nftToken_content_nft_mint}> )}
<div </>
className={classes.nftToken_content_nft_mint_btn} )}
</div>
</div>
<div className={classes.invite}>
<div className={classes.invite_top}>
<span>{t("邀请")}</span>
{address && (
<span
onClick={() => {
navigate("/invitationlist");
}}
>
{t("邀请列表")}{" "}
<IconFont
name="chevronsrightshuangyoujiantou"
color={"#2DFCFC"}
/>
</span>
)}
</div>
<div className={classes.invite_content}>
<span>{t("邀请链接")}</span>
<div className={classes.invite_content_link}>
{address ? (
<>
{userData?.nodeNumber && userData?.nodeNumber > 0 ? (
<>
<span>{shortenString(userInviteLink, 15, 15)}</span>
<IconFont
onClick={() => { onClick={() => {
navigate("/mint"); copyText(userInviteLink);
}} }}
> className={classes.invite_content_icon}
<span>{t("铸造 NFT")}</span> name="fuzhi"
<IconFont color={"#fff"}
className={ />{" "}
classes.nftToken_content_nft_mint_btn_icon </>
} ) : (
name="chevronsrightshuangyoujiantou" <>
color={"#fff"} <span>
/> {t("Buy Edge AI Node to get invitation link")}
</div> </span>
<span>{t("铸造 NFT 获得代币空投")}</span> </>
</div>
)} )}
</> </>
) : ( ) : (
<> <>
<div className={classes.nftToken_content_userDisconnect}> <span>{t("Link wallet to get invitation link")}</span>
<span>{t("钱包未链接,无法向您显示 NFT")}</span>
</div>
</> </>
)} )}
</>
)}
{tabIndex == 1 && (
<div className={classes.nftToken_content_token}>
<div className={classes.nftToken_content_token_top}>
<span>{t("总收益= 已领取 + 待领取")}</span>
</div>
<ul className={classes.nftToken_content_token_list}>
{userData?.userIncomes.map((v, i) => (
<ReceiveCom
key={i}
tokenName={v.coinName}
tokenNum={v.receive}
toReceive={v.collection}
onAssetRec={() => {
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.time,
orderInfo?.orderNumber,
orderInfo.hash
)
.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) && <Empty />}
</ul>
</div> </div>
)}
</div>
</div>
<div className={classes.invite}> <span>
<div className={classes.invite_top}> {t(
<span>{t("邀请")}</span> "Invite your friends to become YOTTA nodes and you will get a 20% rebate."
{address && ( )}
<span
onClick={() => {
navigate("/invitationlist");
}}
>
{t("邀请列表")}{" "}
<IconFont
name="chevronsrightshuangyoujiantou"
color={"#F3BE3C"}
/>
</span> </span>
)}
</div>
<div className={classes.invite_content}>
<span>{t("邀请链接")}</span>
<div className={classes.invite_content_link}>
{address ? (
<>
{userData?.nftId ? (
<>
<span>{shortenString(userInviteLink, 15, 15)}</span>
<IconFont
onClick={() => {
copyText(userInviteLink);
}}
className={classes.invite_content_icon}
name="fuzhi"
color={"#fff"}
/>{" "}
</>
) : (
<>
<span>{t("MINT Nft 获取邀请链接")}</span>
</>
)}
</>
) : (
<>
<span>{t("链接钱包获取邀请链接")}</span>
</>
)}
</div> </div>
<span>
{t(
"普通会员每邀请铸造一个NFT可获得一份空投福利推荐铸造20个NFT的可升级为会长团队中拥有20位会长可升级为基金会社长邀请越多级别越高福利越多。"
)}
</span>
</div> </div>
</div> </div>
</PullToRefresh>
<div className={classes.dataDisclosure}>
<span>{t("数据披露")}</span>
<ul className={classes.dataDisclosure_content}>
<li className={classes.dataDisclosure_content_item}>
<span>{t("资金池")}</span>
<span>{userData?.pools || 0}</span>
</li>
<li className={classes.dataDisclosure_content_item}>
<span>{t("社长席位")}</span>
<span>{userData?.president || 0}</span>
</li>
<li className={classes.dataDisclosure_content_item}>
<span>{t("基金会社长席位")}</span>
<span>{userData?.foundation || 0}</span>
</li>
</ul>
</div>
</div>
</> </>
); );
} }
function ReceiveCom({
tokenName,
tokenNum,
toReceive,
onAssetRec,
onReceive,
}: {
tokenName: string;
tokenNum: number;
toReceive: number;
onAssetRec: () => void;
onReceive: () => void;
}) {
const { t } = useTranslation();
return (
<li className={classes.nftToken_content_token_item}>
{tokenName.toUpperCase() == "USDT" && <img src={usdtBg} alt="" />}
<div>
<span className={classes.nftToken_content_token_item_tokenName}>
{tokenName}
</span>
<span className={classes.nftToken_content_token_item_tokenNum}>
{tokenNum}
</span>
<span
className={classes.nftToken_content_token_item_AssetRecords}
onClick={() => {
onAssetRec();
}}
>
{t("收益记录")}{" "}
<IconFont name="chevronsrightshuangyoujiantou" color={"#3680FF"} />
</span>
</div>
<div>
<div className={classes.nftToken_content_token_item_tokenWaiting}>
<span>{t("待领取")}</span>
<span>{tokenNum + toReceive}</span>
</div>
<Button
className={classes.nftToken_content_token_item_tokenReceive}
onClick={() => onReceive()}
fill="outline"
disabled={toReceive == 0}
>
<span>{t("领取")}</span>
</Button>
</div>
</li>
);
}

View File

@ -1,54 +1,50 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-19 11:03:01 * @Date: 2024-06-19 11:03:01
* @LastEditTime: 2024-06-25 18:17:18 * @LastEditTime: 2024-06-27 15:33:01
* @Author: John * @Author: John
*/ */
import { api_preprelion_list } from "@/server/api"; import usePagination from "@/hook/usePagination";
import { PreprelionListItem } from "@/server/module"; import { api_recommended_list } from "@/server/api";
import { getLevelName } from "@/utils"; import { RecommendedListItem } from "@/server/module";
import { Empty } from "antd-mobile"; import { Empty, InfiniteScroll } from "antd-mobile";
import { useEffect, useState } from "react";
import DataTable, { TableColumn } from "react-data-table-component"; import DataTable, { TableColumn } from "react-data-table-component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export default function () { export default function () {
const { t } = useTranslation(); const { t } = useTranslation();
const [data, setData] = useState<PreprelionListItem[]>([]); const columns: TableColumn<RecommendedListItem>[] = [
const columns: TableColumn<PreprelionListItem>[] = [
{ {
name: t("地址"), name: t("地址"),
selector: (row) => row.address, selector: (row) => row.walletAddress,
grow: 4, grow: 9,
}, },
{ {
name: t("级别"), name: "Node",
grow: 4, selector: (row) => row.nodeNumber,
cell(row, rowIndex, column, id) { grow: 1,
return <div>{getLevelName(row.level)}</div>;
},
},
{
name: t("直推NFT"),
selector: (row) => row.mintNumber,
// @ts-ignore // @ts-ignore
right: "true", right: "true",
grow: 2,
}, },
]; ];
useEffect(() => { const { list, hasMore, loadMore } = usePagination<RecommendedListItem>({
(async () => { service({ pageNum, pageSize }) {
const { data } = await api_preprelion_list().send({}); return new Promise(async (reslove) => {
setData(data?.data || []); const { data } = await api_recommended_list().send({
})(); queryParams: { pageNum, pageSize },
});
return () => {}; reslove(data?.data.records || []);
}, []); });
},
});
return ( return (
<> <>
<DataTable columns={columns} data={data} noDataComponent={<Empty />} /> <DataTable columns={columns} data={list} noDataComponent={<Empty />} />
<InfiniteScroll loadMore={loadMore} hasMore={hasMore}>
<span>no more data</span>
</InfiniteScroll>
</> </>
); );
} }

View File

@ -1,303 +0,0 @@
.container {
padding: 0 14px;
}
.LevelUp {
padding-bottom: 32px;
.content {
margin-top: 16px;
.content_box {
display: flex;
justify-content: space-between;
align-items: center;
.box_item {
/* 自动布局子元素 */
width: 153px;
height: 90px;
border-radius: 12px;
opacity: 1;
/* 自动布局 */
display: flex;
flex-direction: column;
align-items: center;
gap: 7px;
padding: 26px 23px;
background: #171719;
box-shadow: 0px 4px 10px 0px rgba(138, 29, 19, 0.3),
inset 0px 0px 6px 0px #8a1d13;
z-index: 0;
box-sizing: border-box;
span {
white-space: nowrap;
&:nth-of-type(1) {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 10px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #4d4d4d;
z-index: 0;
}
&:nth-of-type(2) {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 14px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 1;
}
}
}
.box_arrow {
width: 24px;
height: 24px;
}
}
.content_price {
display: flex;
align-items: center;
gap: 4px;
margin-top: 16px;
padding: 0 10px;
span {
&:nth-of-type(1) {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 10px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 0;
}
&:nth-of-type(2) {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 10px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #fc872b;
z-index: 1;
}
}
}
.content_price_des {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 10px;
padding: 0 10px;
span {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 10px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #4d4d4d;
z-index: 1;
}
}
.content_btn {
/* 自动布局子元素 */
width: 331px;
height: 40px;
border-radius: 10px;
opacity: 1;
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 11px 40px;
gap: 10px;
background: #fc872b;
z-index: 3;
box-sizing: border-box;
margin: 0 auto;
margin-top: 14px;
span {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 14px;
font-weight: 500;
line-height: normal;
text-align: center;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 0;
}
.icon {
width: 16px;
height: 16px;
}
&[disabled] {
/* 自动布局子元素 */
background: #666666;
}
}
}
.upgrade_conditions {
margin-top: 26px;
> span {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 14px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 0;
}
> ul {
display: flex;
flex-direction: column;
gap: 14px;
margin-top: 16px;
> li {
/* 自动布局子元素 */
width: 344px;
border-radius: 16px;
opacity: 1;
/* 自动布局 */
display: flex;
flex-direction: column;
padding: 15px 10px;
background: #171719;
z-index: 0;
gap: 10px;
box-sizing: border-box;
div {
display: flex;
align-items: center;
gap: 6px;
span {
&:nth-of-type(1) {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 12px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
color: #fc872b;
z-index: 0;
}
&:nth-of-type(2) {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 12px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
color: #ffffff;
z-index: 1;
}
}
}
> ul {
display: flex;
flex-direction: column;
gap: 4px;
li {
/* 自动布局子元素 */
opacity: 1;
font-family: DM Sans;
font-size: 12px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #eaeaea;
z-index: 0;
}
}
}
}
}
}

View File

@ -1,275 +0,0 @@
import IconFont from "@/components/iconfont";
import classes from "./LevelUp.module.css";
import { cn, getLevelName } from "@/utils";
import Button from "antd-mobile/es/components/button";
import Space from "antd-mobile/es/components/space";
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<UserUpgradeInformation>();
const [approveUsdt, setApproveUsdt] = useState<bigint>(0n);
const [balance, setBalance] = useState<bigint>(0n);
const approveLoadingToast = useRef<ToastHandler>();
const approvePrice = useMemo(
() => BigInt(toWei(userUpgradeInfo?.price || "0", "ether")),
[userUpgradeInfo?.price]
);
const upgradeLoadingtoast = useRef<ToastHandler>();
const orderInfo = useRef<UpgradeOrder>();
const {
transcationStatus,
startPollingCheckBuyStatus,
stopPollingCheckBuyStatus,
} = 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();
stopPollingCheckBuyStatus();
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, ...{ status: 1 } });
setUserUpgradeInfo(data?.data);
}
return (
<>
<div className={cn(classes.LevelUp, classes.container)}>
<div className={classes.content}>
<div className={classes.content_box}>
<div className={classes.box_item}>
<span>{t("当前级别")}</span>
<span>{getLevelName(userUpgradeInfo?.level || 0)}</span>
</div>
<IconFont
className={classes.box_arrow}
color="#fff"
name="chevronsrightshuangyoujiantou"
/>
<div className={classes.box_item}>
<span>{t("提升级别")}</span>
<span>{userUpgradeInfo?.level == 1 ? t("社长") : t("无")}</span>
</div>
</div>
<div className={classes.content_price}>
<span>{t("当前升级价格:")}</span>
<span>{userUpgradeInfo?.price || 0} USDT</span>
</div>
<div className={classes.content_price_des}>
<span>{t("价格说明:")}</span>
<span>
{t(
"升级费用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,
}
)}
</span>
</div>
<Button
className={classes.content_btn}
fill="none"
disabled={userUpgradeInfo?.status != 1}
onClick={async () => {
if (balance <= 0n) {
Toast.show({ icon: "fail", content: t("余额不足") });
return;
}
// 授权
if (approveUsdt === 0n) {
approveLoadingToast.current = Toast.show({
icon: "loading",
duration: 0,
content: t("正在授权USDT"),
});
authorizedU(approvePrice)
.then(async () => {
setApproveUsdt(await getApproveUsdt());
approveLoadingToast.current?.close();
})
.catch((err) => {
Toast.show({ content: err.shortMessage, icon: "fail" });
});
return;
}
// 升级
upgradeLoadingtoast.current = Toast.show({
icon: "loading",
duration: 0,
content: t("升级中"),
maskClickable: false,
});
const { data: orderRes } = await api_upgrade().send({});
orderInfo.current = orderRes?.data;
if (!orderInfo.current?.orderNumber) return;
const buyAmount = BigInt(orderInfo.current?.buyAmount || "");
upGradeByContract(buyAmount, orderInfo.current?.orderNumber)
.then((hash) => {
console.log("升级成功hash:", hash);
updateUserUpgrdeInfo();
startPollingCheckBuyStatus(hash);
})
.catch(async (err: BaseError) => {
upgradeLoadingtoast.current?.close();
Toast.show({ content: err.shortMessage, icon: "fail" });
// 取消购买
await api_users_cancel_orders().send({
queryParams: { orderId: orderInfo.current?.id! },
});
});
}}
>
<Space>
{userUpgradeInfo?.status == 1 ? (
<>
{approveUsdt > 0n && <span>{t("升级")}</span>}
{approveUsdt === 0n && <span>{t("授权USDT")}</span>}
</>
) : (
<>
<span>{t("无级别提升")}</span>
</>
)}
</Space>
</Button>
</div>
<div className={classes.upgrade_conditions}>
<span>{t("升级条件")}</span>
<ul>
<ConItem
memberName={t("普通会员")}
limitText={t("(限量xxx个)", {
value: userUpgradeInfo?.ordinary || 0,
})}
conList={[
t("1. MINT一枚NFT成为非活跃普通会员"),
t("2. MINT NFT并推荐MINT成为活跃普通会员"),
t("3. 享1%代币空投平分(所有普通会员)"),
t("4. 活跃普通会员根据推荐MINT数量额外有空投权益。"),
]}
/>
<ConItem
memberName={t("社长")}
limitText={t("(限量xxx个)", {
value: userUpgradeInfo?.president || 0,
})}
conList={[
t("1. 先成为活跃普通会员并推荐MINT 20枚NFT"),
t("2. 需支付100USDT起升级费用成为社长(第二个起递增10%)"),
t("3. 享普通会员权益+0.5%空投代币平分(所有社长)"),
t("4. 享社长升级费用50%平分(扣除2USDT GAS费后)"),
t("5. 从推荐MINT 21枚开始直推的铸造费用归社长所有"),
]}
/>
<ConItem
memberName={t("基金会社长")}
limitText={t("(限量xxx个)", {
value: userUpgradeInfo?.foundation || 0,
})}
conList={[
t("1. 先成为社长并团队中有20个社长"),
t("2. 享社长权益+0.5%空投代币平分(所有基金会社长)"),
t("3. 另外基金会社长参与所有项目分成"),
]}
/>
</ul>
</div>
</div>
</>
);
}
function ConItem({
memberName,
limitText,
conList,
}: PropsWithChildren<{
memberName: string;
limitText: string;
conList: string[];
}>) {
return (
<li>
<div>
<span>{memberName}</span>
<span>{limitText}</span>
</div>
<ul>
{conList.map((v, i) => (
<li key={i}>{v}</li>
))}
</ul>
</li>
);
}

View File

@ -16,7 +16,6 @@
padding: 8px; padding: 8px;
box-sizing: border-box; box-sizing: border-box;
margin-top: 17px;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -42,7 +41,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -59,7 +58,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;
@ -81,12 +80,55 @@
gap: 6px; gap: 6px;
padding: 0 7px; padding: 0 7px;
margin-top: 20px;
span { span {
&:nth-of-type(1) { &:nth-of-type(1) {
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #9e9e9e;
z-index: 0;
}
&:nth-of-type(2) {
/* 自动布局子元素 */
opacity: 1;
font-family: Space Grotesk;
font-size: 14px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #eaeaea;
z-index: 1;
}
}
}
.price {
display: flex;
align-items: center;
gap: 8px;
/* 自动布局子元素 */
> span {
&:nth-of-type(1) {
opacity: 1;
font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -103,15 +145,55 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 16px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 1;
}
}
}
.cost_price {
/* 自动布局子元素 */
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
span {
&:nth-of-type(1) {
font-family: Space Grotesk;
font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
letter-spacing: 0em; letter-spacing: 0em;
font-variation-settings: "opsz" auto; font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on; font-feature-settings: "kern" on;
color: #eaeaea; color: #ffffff;
z-index: 0;
}
&:nth-of-type(2) {
/* 自动布局子元素 */
font-family: Space Grotesk;
font-size: 10px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
z-index: 1; z-index: 1;
} }
@ -120,7 +202,6 @@
.btn { .btn {
/* 自动布局子元素 */ /* 自动布局子元素 */
width: 331px;
height: 40px; height: 40px;
border-radius: 10px; border-radius: 10px;
opacity: 1; opacity: 1;
@ -133,7 +214,7 @@
padding: 11px 40px; padding: 11px 40px;
gap: 10px; gap: 10px;
background: #fc872b; background: #2dfcfc;
z-index: 3; z-index: 3;
@ -145,7 +226,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -154,7 +235,7 @@
font-variation-settings: "opsz" auto; font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on; font-feature-settings: "kern" on;
color: #ffffff; color: #101010;
z-index: 0; z-index: 0;
} }

View File

@ -1,10 +1,10 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-18 15:28:03 * @Date: 2024-06-18 15:28:03
* @LastEditTime: 2024-06-24 18:28:07 * @LastEditTime: 2024-06-27 14:35:27
* @Author: John * @Author: John
*/ */
import { cn } from "@/utils"; import { cn, filterAddressBeforeZero, filterAmountBeforeZero } from "@/utils";
import classes from "./Mint.module.css"; import classes from "./Mint.module.css";
import nft_bg from "@/assets/nft_bg.svg"; import nft_bg from "@/assets/nft_bg.svg";
import Button from "antd-mobile/es/components/button"; import Button from "antd-mobile/es/components/button";
@ -12,18 +12,24 @@ import Space from "antd-mobile/es/components/space";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { import {
api_get_homepage_user_data,
api_get_nft_configuration_data, api_get_nft_configuration_data,
api_nft_order, api_node_order,
api_users_cancel_orders, api_users_cancel_orders,
} from "@/server/api"; } from "@/server/api";
import { NftConfigurationData, NftOrder } from "@/server/module"; import {
NftConfigurationData,
NftOrder,
NodeOrder,
UserHomeData,
} from "@/server/module";
import { import {
authorizedU, authorizedU,
getApproveUsdt, getApproveUsdt,
getBalance, getBalance,
payByContract, payByContract,
} from "@/contract/utils"; } from "@/contract/utils";
import { Dialog, Modal, Toast } from "antd-mobile"; import { Dialog, Modal, Stepper, Toast } from "antd-mobile";
import useUserStore from "@/store/User"; import useUserStore from "@/store/User";
import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus"; import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus";
import { ToastHandler } from "antd-mobile/es/components/toast"; import { ToastHandler } from "antd-mobile/es/components/toast";
@ -33,24 +39,31 @@ import { toWei } from "web3-utils";
export default function () { export default function () {
const { t } = useTranslation(); const { t } = useTranslation();
const { Token } = useUserStore(); const { Token } = useUserStore();
const [nftConfig, setNftConfig] = useState<NftConfigurationData>(); const [nodeConfig, setNodeConfig] = useState<UserHomeData>();
const [approveUsdt, setApproveUsdt] = useState<bigint>(0n); const [approveUsdt, setApproveUsdt] = useState<bigint>(0n);
const [balance, setBalance] = useState<bigint>(0n); const [balance, setBalance] = useState<bigint>(0n);
const orderInfo = useRef<NftOrder>(); const orderInfo = useRef<NodeOrder>();
const navigate = useNavigate(); const navigate = useNavigate();
const [num, setNum] = useState(1);
const buyLoadingToast = useRef<ToastHandler>(); const buyLoadingToast = useRef<ToastHandler>();
const approveLoadingToast = useRef<ToastHandler>(); const approveLoadingToast = useRef<ToastHandler>();
const { buyNftIds, startPollingCheckBuyStatus, stopPollingCheckBuyStatus } = const {
usePollingCheckBuyStatus("NFT"); transcationStatus,
startPollingCheckBuyStatus,
stopPollingCheckBuyStatus,
} = usePollingCheckBuyStatus("NORMAL");
const approvePrice = useMemo( const costNum = useMemo(
() => BigInt(toWei(nftConfig?.nftPrice || "0", "ether")), () =>
[nftConfig?.nftPrice] BigInt(
toWei(`${parseFloat(`${nodeConfig?.nodePrice || "0"}`) * num}`, "ether")
),
[nodeConfig?.nodePrice, num]
); );
useEffect(() => { useEffect(() => {
updateNftConfig(); updateNodeConfig();
return () => {}; return () => {};
}, []); }, []);
@ -66,16 +79,18 @@ export default function () {
return () => {}; return () => {};
}, [Token]); }, [Token]);
async function updateNftConfig() { async function updateNodeConfig() {
const { data } = await api_get_nft_configuration_data().send({}); const { data } = await api_get_homepage_user_data().send({});
setNftConfig(data?.data); setNodeConfig(data?.data);
} }
useEffect(() => { useEffect(() => {
if (buyNftIds) { if (transcationStatus == "success") {
buyLoadingToast.current?.close(); buyLoadingToast.current?.close();
stopPollingCheckBuyStatus(); stopPollingCheckBuyStatus();
Dialog.alert({ Dialog.alert({
content: `${t("MINT成功返回首页查看")}`, content: `${t(
"Buy successful. Please return to the homepage to view."
)}`,
confirmText: "OK", confirmText: "OK",
onConfirm() { onConfirm() {
navigate("/"); navigate("/");
@ -84,7 +99,7 @@ export default function () {
} }
return () => {}; return () => {};
}, [buyNftIds]); }, [transcationStatus]);
useEffect(() => { useEffect(() => {
return () => {}; return () => {};
@ -93,48 +108,41 @@ export default function () {
return ( return (
<> <>
<div className={cn(classes.Mint, classes.container)}> <div className={cn(classes.Mint, classes.container)}>
<div className={classes.des}>
<span>
Introduction to the node Introduction to the node Inion to the node
Introduction to the node Introduction to the node Introduction to
the node Introduction to the node Introduction to the node
Introduction to the node Introduction to the node Introduction to
the node Introduction to the node Introduction to the node
Introduction to the node
</span>
</div>
<div className={classes.nftImg}> <div className={classes.nftImg}>
<img src={nft_bg} alt="" /> <img src={nft_bg} alt="" />
</div> </div>
<ul> <div className={classes.price}>
<li> <span>Price:</span>
<span>{t("NFT总量")}</span> <span>{nodeConfig?.nodePrice} USDT</span>
<span>{nftConfig?.nftCount || 0}</span> </div>
</li>
<li> <Stepper
<span>{t("MINT余量")}</span> value={num}
<span>{nftConfig?.nftRemainder || 0}</span> defaultValue={1}
</li> onChange={(value) => {
if (value <= 0) return;
setNum(value);
}}
style={{
"--button-text-color": "#101010",
}}
/>
<li> <div className={classes.cost_price}>
<span>{t("当前MINT价格")}</span> <span>Cost:</span>
<span>{nftConfig?.nftPrice || 0} USDT</span>
</li>
</ul>
<div className={classes.des}>
<span>{t("价格说明:")}</span>
<span> <span>
{t( {parseFloat(`${nodeConfig?.nodePrice || "0"}`) * num} USDT
"{{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,
}
)}
</span> </span>
</div> </div>
@ -154,7 +162,7 @@ export default function () {
duration: 0, duration: 0,
content: t("正在授权USDT"), content: t("正在授权USDT"),
}); });
authorizedU(approvePrice) authorizedU(costNum)
.then(async () => { .then(async () => {
setApproveUsdt(await getApproveUsdt()); setApproveUsdt(await getApproveUsdt());
approveLoadingToast.current?.close(); approveLoadingToast.current?.close();
@ -173,18 +181,46 @@ export default function () {
content: t("购买中"), content: t("购买中"),
maskClickable: false, maskClickable: false,
}); });
const { data: orderRes } = await api_nft_order().send({}); const { data: orderRes } = await api_node_order().send({
data: {
number: num,
},
});
orderInfo.current = orderRes?.data; orderInfo.current = orderRes?.data;
if (!orderInfo.current?.orderNumber) return; if (!orderInfo.current?.orderNumber) return;
const buyAmount = BigInt(orderInfo.current?.buyAmount || ""); const {
collectionAddress,
collectionAmount,
addressOne,
addressTwo,
addressThree,
addressFour,
awardOne,
awardTwo,
awardThree,
awardFour,
} = orderInfo.current;
payByContract( payByContract(
buyAmount, filterAddressBeforeZero([
orderInfo.current?.orderNumber, collectionAddress,
orderInfo.current.payInduction addressOne,
addressTwo,
addressThree,
addressFour,
]),
filterAmountBeforeZero([
BigInt(toWei(`${collectionAmount}`, "ether")),
BigInt(toWei(`${awardOne}`, "ether")),
BigInt(toWei(`${awardTwo}`, "ether")),
BigInt(toWei(`${awardThree}`, "ether")),
BigInt(toWei(`${awardFour}`, "ether")),
]),
orderInfo.current.orderNumber,
orderInfo.current.time
) )
.then((hash) => { .then((hash) => {
console.log("购买成功hash:", hash); console.log("购买成功hash:", hash);
updateNftConfig(); updateNodeConfig();
startPollingCheckBuyStatus(hash); startPollingCheckBuyStatus(hash);
}) })
.catch(async (err: BaseError) => { .catch(async (err: BaseError) => {
@ -194,14 +230,14 @@ export default function () {
icon: "fail", icon: "fail",
}); });
// 取消购买 // 取消购买
await api_users_cancel_orders().send({ // await api_users_cancel_orders().send({
queryParams: { orderId: orderInfo.current?.id! }, // queryParams: { orderId: orderInfo.current?.id! },
}); // });
}); });
}} }}
> >
<Space> <Space>
{approveUsdt > 0n && <span>MINT</span>} {approveUsdt > 0n && <span>Buy</span>}
{approveUsdt === 0n && <span>{t("授权USDT")}</span>} {approveUsdt === 0n && <span>{t("授权USDT")}</span>}
</Space> </Space>
</Button> </Button>

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-18 10:28:21 * @Date: 2024-06-18 10:28:21
* @LastEditTime: 2024-06-25 14:47:34 * @LastEditTime: 2024-06-27 09:52:45
* @Author: John * @Author: John
*/ */
import { GET, POST } from "./client"; import { GET, POST } from "./client";
@ -11,7 +11,9 @@ import {
IncomeRecordType, IncomeRecordType,
NftConfigurationData, NftConfigurationData,
NftOrder, NftOrder,
NodeOrder,
PreprelionListItem, PreprelionListItem,
RecommendedList,
UpgradeOrder, UpgradeOrder,
UserHomeData, UserHomeData,
UserIncome, UserIncome,
@ -20,7 +22,7 @@ import {
// 检查账号是否注册 // 检查账号是否注册
export function api_check_account_registration() { export function api_check_account_registration() {
return GET<{ account: string }, { exist: boolean }>({ return GET<{ account: string; chainType: 2 }, { exist: boolean }>({
url: "/api/account/exist", url: "/api/account/exist",
requiresToken: false, requiresToken: false,
}); });
@ -69,9 +71,7 @@ export function api_get_wallet_signature_string() {
// 获取首页用户数据 // 获取首页用户数据
export function api_get_homepage_user_data() { export function api_get_homepage_user_data() {
return GET<any, UserHomeData>({ return GET<any, UserHomeData>({
url: "/api/common/getUserData", url: "/api/user-node/getHomeData",
requiresToken: false,
requiresAddress: false,
}); });
} }
@ -106,8 +106,8 @@ export function api_pagling_query_income_record() {
} }
// NFT下单 // NFT下单
export function api_nft_order() { export function api_node_order() {
return POST<any, NftOrder>({ url: "/api/nft/payNft" }); return POST<{ number: number }, NodeOrder>({ url: "/api/user-node/payNode" });
} }
// 用户取消订单告诉我 // 用户取消订单告诉我
@ -115,9 +115,11 @@ export function api_users_cancel_orders() {
return POST<any, any, { orderId: number }>({ url: "/api/nft/cancel" }); return POST<any, any, { orderId: number }>({ url: "/api/nft/cancel" });
} }
// 直推列表 // 推荐列表
export function api_preprelion_list() { export function api_recommended_list() {
return GET<any, PreprelionListItem[]>({ url: "/api/user/getDirectPushList" }); return GET<{ pageNum: number; pageSize: number }, RecommendedList>({
url: "/api/user-node/recommendedLists",
});
} }
// 升级 // 升级
@ -145,3 +147,10 @@ export function api_query_whether_the_user_is_binding_relationship() {
url: "/api/account/bindOrNot", url: "/api/account/bindOrNot",
}); });
} }
// 查询用户邀请码
export function api_query_user_invitation_code() {
return GET<any, { invitationCode: string }>({
url: "/api/invite/invitationCode",
});
}

View File

@ -5,21 +5,16 @@ export type BASE_RESPONSE<T = any> = {
timeMillis: number; timeMillis: number;
}; // What's returned from request }; // What's returned from request
export type Level = 0 | 1 | 2 | 3; // 0=无等级 1=会员 2=社长 3=基金会 export type Level = 0 | 1 | 2 | 3 | 4; // 0=无等级 1=区代理 2=市代理 3=省代理 4=超级节点
export interface UserHomeData { export interface UserHomeData {
address: string; address: string;
airdropNumber: number; directPushNode: number;
foundation: number;
invitationCode: string;
level: Level; level: Level;
mintNumber: number; nodeNumber: number;
nftId: number; nodePrice: string;
pools: string; revenueUsdt: string;
president: number; teamNode: number;
presidentNumber: number;
userImg: string; userImg: string;
userIncomes: UserIncome[];
active: 0 | 1; // "0=非活跃 1=活跃用户"
} }
export interface UserIncome { export interface UserIncome {
coinId: number; coinId: number;
@ -137,3 +132,52 @@ export interface ClaimIncome {
orderNumber: string; orderNumber: string;
time: number; time: number;
} }
export interface RecommendedList {
countId: string;
current: number;
maxLimit: string;
optimizeCountSql: boolean;
orders: Order[];
pages: number;
records: RecommendedListItem[];
searchCount: boolean;
size: number;
total: number;
}
export interface RecommendedListItem {
walletAddress: string;
nodeNumber: number;
promotionUsdt: string;
nodeCount: number;
}
export interface NodeOrder {
time: number;
addressFour: string;
addressOne: string;
addressThree: string;
addressTwo: string;
awardFour: string;
awardOne: string;
awardThree: string;
awardTwo: string;
buyAmount: string;
collectionAddress: string;
collectionAmount: string;
createBy: string;
createTime: number;
dataJson: string;
hash: string;
id: number;
illustrate: string;
nodeNumber: number;
nodeSettingId: number;
orderNumber: string;
payCoin: string;
status: number;
updateBy: string;
updateTime: string;
userId: number;
walletAddress: string;
}

View File

@ -3,13 +3,13 @@
border-bottom: 0.25px solid #333333; border-bottom: 0.25px solid #333333;
.adm-tabs-tab-line { .adm-tabs-tab-line {
background-color: #fc872b; background-color: #2dfcfc;
} }
.adm-tabs-tab { .adm-tabs-tab {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -51,7 +51,7 @@
opacity: 1; opacity: 1;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #fc872b; border: 1px solid #2dfcfc;
z-index: 1; z-index: 1;
background-color: transparent; background-color: transparent;
@ -59,7 +59,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: 25px; line-height: 25px;
@ -80,14 +80,14 @@
border-radius: 8px; border-radius: 8px;
opacity: 1; opacity: 1;
background: #fc872b; background: #2dfcfc;
z-index: 0; z-index: 0;
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
line-height: 25px; line-height: 25px;
@ -111,7 +111,7 @@
.adm-picker-header-title { .adm-picker-header-title {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 17px; font-size: 17px;
font-weight: bold; font-weight: bold;
line-height: 24px; line-height: 24px;
@ -124,7 +124,7 @@
.adm-picker-header-button { .adm-picker-header-button {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 17px; font-size: 17px;
font-weight: 500; font-weight: 500;
line-height: 24px; line-height: 24px;
@ -147,7 +147,7 @@
.adm-picker-view-column-item { .adm-picker-view-column-item {
/* height: 34px; */ /* height: 34px; */
.adm-picker-view-column-item-label { .adm-picker-view-column-item-label {
/* font-family: DM Sans; /* font-family: Space Grotesk;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
line-height: 34px; line-height: 34px;
@ -192,7 +192,7 @@
/* background: rgba(252, 135, 43, 0.5) !important; */ /* background: rgba(252, 135, 43, 0.5) !important; */
box-sizing: border-box !important; box-sizing: border-box !important;
border: 1px solid #fc872b !important; border: 1px solid #2dfcfc !important;
/* backdrop-filter: blur(10px); */ /* backdrop-filter: blur(10px); */
@ -200,7 +200,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -240,7 +240,7 @@
/* 自动布局子元素 */ /* 自动布局子元素 */
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
line-height: normal; line-height: normal;
@ -256,3 +256,104 @@
} }
} }
} }
/* adm-stepper */
.adm-stepper {
width: calc(100%) !important;
height: 42px;
border-radius: 5px !important;
opacity: 1;
background: rgba(37, 33, 39, 0.2);
box-sizing: border-box !important;
border: 1px solid #2dfcfc !important;
.adm-button {
width: 42px;
height: 42px;
border-radius: 5px;
opacity: 1;
background: #2dfcfc;
svg {
width: 23px;
height: 23px;
}
}
.adm-stepper-middle {
.adm-stepper-input {
height: 44px;
background-color: transparent;
.adm-input-element {
opacity: 1;
font-family: Space Grotesk;
font-size: 22px;
font-weight: bold;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #ffffff;
}
}
}
}
.adm-dialog {
.adm-center-popup-body {
box-sizing: border-box !important;
border: 1px solid #2dfcfc !important;
border-radius: 10px !important;
background-color: transparent !important;
backdrop-filter: blur(10px);
.adm-dialog-content {
.adm-auto-center-content {
opacity: 1;
font-family: Space Grotesk;
font-size: 14px;
font-weight: 500;
line-height: normal;
letter-spacing: 0px;
font-variation-settings: "opsz" auto;
color: #ffffff;
z-index: 0;
}
}
.adm-dialog-footer {
.adm-dialog-action-row {
border-top: 1px solid #2dfcfc !important;
.adm-dialog-button {
span {
color: #2dfcfc;
}
}
}
}
}
}
/* adm-pull-to-refresh */
.adm-pull-to-refresh {
.adm-pull-to-refresh-head-content {
/* 自动布局子元素 */
font-family: Space Grotesk;
font-size: 14px;
font-weight: 500;
line-height: normal;
letter-spacing: 0em;
font-variation-settings: "opsz" auto;
font-feature-settings: "kern" on;
color: #9e9e9e;
z-index: 0;
}
}

View File

@ -16,7 +16,7 @@
div { div {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
@ -41,7 +41,7 @@
div { div {
opacity: 1; opacity: 1;
font-family: DM Sans; font-family: Space Grotesk;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: normal; line-height: normal;

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-17 18:19:27 * @Date: 2024-06-17 18:19:27
* @LastEditTime: 2024-06-25 15:29:51 * @LastEditTime: 2024-06-27 11:40:59
* @Author: John * @Author: John
*/ */
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from "clsx";
@ -87,14 +87,37 @@ export function cn(...inputs: ClassValue[]) {
export function getLevelName(level: Level) { export function getLevelName(level: Level) {
switch (level) { switch (level) {
case 0: case 0:
return i18next.t("普通非活跃"); return "No Node";
case 1: case 1:
return i18next.t("普通活跃"); return "Regional agent";
case 2: case 2:
return i18next.t("社长"); return "Municipal agent";
case 3: case 3:
return i18next.t("基金会社长"); return "Provincial agent";
case 4:
return "Super Node";
default: default:
break; break;
} }
} }
export function filterAddressBeforeZero(arr: string[]) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === "0") {
break;
}
result.push(arr[i]);
}
return result;
}
export function filterAmountBeforeZero(arr: bigint[]) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === 0n) {
break;
}
result.push(arr[i]);
}
return result;
}

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-19 15:55:07 * @Date: 2024-06-19 15:55:07
* @LastEditTime: 2024-06-25 14:47:58 * @LastEditTime: 2024-06-27 15:09:45
* @Author: John * @Author: John
*/ */
import { config } from "@/components/WalletProvider"; import { config } from "@/components/WalletProvider";
@ -89,7 +89,7 @@ export async function signAndLogin(address?: `0x${string}`): Promise<void> {
}); });
const { data: isExitData } = await api_check_account_registration().send({ const { data: isExitData } = await api_check_account_registration().send({
queryParams: { account: address }, queryParams: { account: address, chainType: 2 },
}); });
if (isExitData?.data?.exist) { if (isExitData?.data?.exist) {
// 登录 // 登录
@ -106,8 +106,6 @@ export async function signAndLogin(address?: `0x${string}`): Promise<void> {
}); });
} catch (error) { } catch (error) {
// 用户拒绝签名或者遇到错误,断开链接 // 用户拒绝签名或者遇到错误,断开链接
const { connector } = getAccount(config);
await disconnect(config, { connector });
loadingToast.close(); loadingToast.close();
loginOut(); loginOut();
throw new Error("用户拒绝签名或者遇到错误,断开链接"); throw new Error("用户拒绝签名或者遇到错误,断开链接");
@ -128,32 +126,24 @@ export async function signAndLogin(address?: `0x${string}`): Promise<void> {
return { ...state, Token: loginInfoData.data?.token }; return { ...state, Token: loginInfoData.data?.token };
}); });
// TODO 判断用户是否绑定关系✔
const { data } =
await api_query_whether_the_user_is_binding_relationship().send({});
if (
typeof data?.data.result == "boolean" &&
data?.data.result === false
) {
const inviteCode = getUrlQueryParam(UrlQueryParamsKey.INVITE_CODE);
if (inviteCode) {
const { data } = await api_binding_invitation_relationship().send({
data: {
shareCode: inviteCode,
},
});
}
}
reslove(); reslove();
loadingToast.close(); loadingToast.close();
} }
} else { } else {
const inviteCode = getUrlQueryParam(UrlQueryParamsKey.INVITE_CODE);
if (!inviteCode) {
Toast.show({
icon: "fail",
content: i18next.t("invalid invitation link"),
});
return loginOut();
}
// 注册 // 注册
await api_signUp().send({ await api_signUp().send({
data: { data: {
account: address, account: address,
publicKey, publicKey,
shareCode: "", shareCode: inviteCode,
chainType: 2, chainType: 2,
}, },
}); });
@ -164,7 +154,9 @@ export async function signAndLogin(address?: `0x${string}`): Promise<void> {
}); });
} }
export function loginOut() { export async function loginOut() {
const { connector } = getAccount(config);
await disconnect(config, { connector });
useUserStore.setState((state) => { useUserStore.setState((state) => {
return { ...state, Address: "", Token: "" }; return { ...state, Address: "", Token: "" };
}); });

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-17 17:20:03 * @Date: 2024-06-17 17:20:03
* @LastEditTime: 2024-06-24 10:08:56 * @LastEditTime: 2024-06-26 17:50:12
* @Author: John * @Author: John
*/ */
import { defineConfig } from "vite"; import { defineConfig } from "vite";
@ -16,7 +16,7 @@ export default defineConfig({
host: "192.168.10.167", host: "192.168.10.167",
proxy: { proxy: {
"/dev": { "/dev": {
target: "http://192.168.10.106:8100", target: "http://192.168.10.106:8102",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/dev/, ""), rewrite: (path) => path.replace(/^\/dev/, ""),
}, },