feat:

This commit is contained in:
john 2024-06-28 18:00:45 +08:00
parent c1a8cfd6d6
commit a58b225544
15 changed files with 582 additions and 422 deletions

View File

@ -1,7 +1,7 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-17 18:25:10 * @Date: 2024-06-17 18:25:10
* @LastEditTime: 2024-06-19 16:14:06 * @LastEditTime: 2024-06-28 17:46:23
* @Author: John * @Author: John
*/ */
export default { export default {
@ -11,7 +11,7 @@ export default {
rootValue: 37.5, rootValue: 37.5,
propList: ["*"], propList: ["*"],
exclude: (e) => { exclude: (e) => {
if (/.*-m\.css$/.test(e) || /.module\.css$/.test(e)) { if (/.*-m\.css$/.test(e) || /.*-m.module\.css$/.test(e)) {
// console.log(e); // console.log(e);
return false; return false;
} }

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-26 16:00:00 * @LastEditTime: 2024-06-28 17:44:02
* @Author: John * @Author: John
*/ */
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
@ -16,7 +16,7 @@ import InvitationList from "./pages/InvitationList";
import { useEffect } from "react"; import { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useUserStore from "./store/User"; import useUserStore from "./store/User";
import { getUrlQueryParam } from "./utils"; import { getUrlQueryParam, isMobile } from "./utils";
import { UrlQueryParamsKey } from "./constants"; import { UrlQueryParamsKey } from "./constants";
import { signAndLogin } from "./utils/wallet"; import { signAndLogin } from "./utils/wallet";
import { useAccount } from "wagmi"; import { useAccount } from "wagmi";
@ -45,12 +45,30 @@ function App() {
return ( return (
<> <>
<RouterLogProvider> <RouterLogProvider>
<Header /> {isMobile ? (
<>
<Header.Mobile />
<Routes> <Routes>
<Route path="/" element={<Home />} /> <>
<Route path="/" element={<Home.Mobile />} />
<Route path="/mint" element={<Mint />} /> <Route path="/mint" element={<Mint />} />
<Route path="/invitationlist" element={<InvitationList />}></Route> <Route
path="/invitationlist"
element={<InvitationList />}
></Route>
</>
</Routes> </Routes>
</>
) : (
<>
<Header.Desktop />
<Routes>
<>
<Route path="/" element={<Home.Desktop />} />
</>
</Routes>
</>
)}
</RouterLogProvider> </RouterLogProvider>
</> </>
); );

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-27 15:29:00 * @LastEditTime: 2024-06-28 14:05:29
* @Author: John * @Author: John
*/ */
import Picker, { import Picker, {
@ -11,7 +11,7 @@ import Picker, {
import { useContext, useEffect, useMemo, useState } from "react"; import { useContext, useEffect, useMemo, useState } from "react";
import IconFont from "./iconfont"; import IconFont from "./iconfont";
import logo from "@/assets/logo.svg"; import logo from "@/assets/logo.svg";
import classes from "./Header.module.css"; import classes from "./Header-m.module.css";
import { useLocation, useNavigate, useNavigation } from "react-router-dom"; import { useLocation, useNavigate, useNavigation } from "react-router-dom";
import { RouterLogContext } from "@/context/RouterContext"; import { RouterLogContext } from "@/context/RouterContext";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -27,7 +27,7 @@ const langColums: Action[] = [
{ text: "日本語", key: Lang.jp }, { text: "日本語", key: Lang.jp },
{ text: "DEUTSCH", key: Lang.de }, { text: "DEUTSCH", key: Lang.de },
]; ];
export default function () { function Mobile() {
const { UpdateLang } = useUserStore(); const { UpdateLang } = useUserStore();
const route = useContext(RouterLogContext); const route = useContext(RouterLogContext);
@ -79,3 +79,12 @@ export default function () {
</> </>
); );
} }
function Desktop() {
return <></>;
}
export default {
Mobile,
Desktop,
};

View File

@ -1,10 +1,10 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-19 10:49:42 * @Date: 2024-06-19 10:49:42
* @LastEditTime: 2024-06-19 10:52:32 * @LastEditTime: 2024-06-28 14:05:45
* @Author: John * @Author: John
*/ */
import classes from "./RecordsItem.module.css"; import classes from "./RecordsItem-m.module.css";
export default function RecordsItem({ export default function RecordsItem({
itemList, itemList,
}: { }: {

View File

@ -0,0 +1,103 @@
/*
* @LastEditors: John
* @Date: 2024-06-28 14:09:55
* @LastEditTime: 2024-06-28 14:14:29
* @Author: John
*/
import useUserStore from "@/store/User";
import { useWeb3Modal } from "@web3modal/wagmi/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAccount } from "wagmi";
import { useNavigate } from "react-router-dom";
import { Dialog } from "antd-mobile";
import {
api_get_homepage_user_data,
api_query_user_invitation_code,
} from "@/server/api";
import { UserHomeData } from "@/server/module";
import { UrlQueryParamsKey } from "@/constants";
import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus";
import { ToastHandler } from "antd-mobile/es/components/toast";
import { PullStatus } from "antd-mobile/es/components/pull-to-refresh";
export default function () {
const { Token, UpdateToken } = useUserStore();
const { open } = useWeb3Modal();
const { t } = useTranslation();
const { address } = useAccount();
const [tabIndex, setTabIndex] = useState(0);
const navigate = useNavigate();
const [userData, setUserData] = useState<UserHomeData>();
const [inviteCode, setInviteCode] = useState("");
const userInviteLink = useMemo(
() =>
`${location.origin}/#/?${UrlQueryParamsKey.INVITE_CODE}=${inviteCode}`,
[inviteCode]
);
const receiveLoadingToast = useRef<ToastHandler>();
const {
transcationStatus,
startPollingCheckBuyStatus,
stopPollingCheckBuyStatus,
} = usePollingCheckBuyStatus("NORMAL");
const statusRecord: Record<PullStatus, string> = {
pulling: "Pull down to refresh",
canRelease: "Release to refresh immediately",
refreshing: "Loading...",
complete: "Refresh complete",
};
useEffect(() => {
if (Token) {
getHomeData();
getInviteCode();
}
return () => {};
}, [Token]);
useEffect(() => {
if (transcationStatus == "success") {
receiveLoadingToast.current?.close();
stopPollingCheckBuyStatus();
Dialog.alert({
content: `${t("领取成功,前往钱包查看")}`,
confirmText: "OK",
});
}
return () => {};
}, [transcationStatus]);
async function getHomeData() {
const { data } = await api_get_homepage_user_data().send({});
setUserData(data?.data);
}
async function getInviteCode() {
const { data } = await api_query_user_invitation_code().send({});
setInviteCode(data?.data.invitationCode || "");
}
useEffect(() => {
console.log("user token:", Token);
return () => {};
}, [Token]);
return {
getHomeData,
statusRecord,
Token,
address,
setUserData,
userData,
tabIndex,
t,
setTabIndex,
navigate,
userInviteLink,
};
}

View File

@ -0,0 +1,41 @@
import usePagination from "@/hook/usePagination";
import { api_recommended_list } from "@/server/api";
import { RecommendedListItem } from "@/server/module";
import { Empty, InfiniteScroll } from "antd-mobile";
import DataTable, { TableColumn } from "react-data-table-component";
import { useTranslation } from "react-i18next";
export default function () {
const { t } = useTranslation();
const columns: TableColumn<RecommendedListItem>[] = [
{
name: t("地址"),
selector: (row) => row.walletAddress,
grow: 9,
},
{
name: "Node",
selector: (row) => row.nodeNumber,
grow: 1,
// @ts-ignore
right: "true",
},
];
const { list, hasMore, loadMore } = usePagination<RecommendedListItem>({
service({ pageNum, pageSize }) {
return new Promise(async (reslove) => {
const { data } = await api_recommended_list().send({
queryParams: { pageNum, pageSize },
});
reslove(data?.data.records || []);
});
},
});
return {
columns,
list,
loadMore,
hasMore,
};
}

View File

@ -0,0 +1,118 @@
import { cn, filterAddressBeforeZero, filterAmountBeforeZero } from "@/utils";
import classes from "./Mint-m.module.css";
import nft_bg from "@/assets/nft_bg.svg";
import Button from "antd-mobile/es/components/button";
import Space from "antd-mobile/es/components/space";
import { useTranslation } from "react-i18next";
import { useEffect, useMemo, useRef, useState } from "react";
import {
api_get_homepage_user_data,
api_get_nft_configuration_data,
api_node_order,
api_users_cancel_orders,
} from "@/server/api";
import {
NftConfigurationData,
NftOrder,
NodeOrder,
UserHomeData,
} from "@/server/module";
import {
authorizedU,
getApproveUsdt,
getBalance,
payByContract,
} from "@/contract/utils";
import { Dialog, Modal, Stepper, Toast } from "antd-mobile";
import useUserStore from "@/store/User";
import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus";
import { ToastHandler } from "antd-mobile/es/components/toast";
import { BaseError } from "wagmi";
import { useNavigate } from "react-router-dom";
import { toWei } from "web3-utils";
export default function () {
const { t } = useTranslation();
const { Token } = useUserStore();
const [nodeConfig, setNodeConfig] = useState<UserHomeData>();
const [approveUsdt, setApproveUsdt] = useState<bigint>(0n);
const [balance, setBalance] = useState<bigint>(0n);
const orderInfo = useRef<NodeOrder>();
const navigate = useNavigate();
const [num, setNum] = useState(1);
const buyLoadingToast = useRef<ToastHandler>();
const approveLoadingToast = useRef<ToastHandler>();
const {
transcationStatus,
startPollingCheckBuyStatus,
stopPollingCheckBuyStatus,
} = usePollingCheckBuyStatus("NORMAL");
const costNum = useMemo(
() =>
BigInt(
toWei(`${parseFloat(`${nodeConfig?.nodePrice || "0"}`) * num}`, "ether")
),
[nodeConfig?.nodePrice, num]
);
useEffect(() => {
updateNodeConfig();
return () => {};
}, []);
useEffect(() => {
(async () => {
Toast.show({ icon: "loading", content: t("正在获取已授权金额") });
setBalance(await getBalance());
setApproveUsdt(await getApproveUsdt());
Toast.clear();
})();
return () => {};
}, [Token]);
async function updateNodeConfig() {
const { data } = await api_get_homepage_user_data().send({});
setNodeConfig(data?.data);
}
useEffect(() => {
if (transcationStatus == "success") {
buyLoadingToast.current?.close();
stopPollingCheckBuyStatus();
Dialog.alert({
content: `${t(
"Buy successful. Please return to the homepage to view."
)}`,
confirmText: "OK",
onConfirm() {
navigate("/");
},
});
}
return () => {};
}, [transcationStatus]);
useEffect(() => {
return () => {};
}, []);
return {
nodeConfig,
num,
setNum,
approveUsdt,
t,
balance,
approveLoadingToast,
costNum,
setApproveUsdt,
buyLoadingToast,
orderInfo,
updateNodeConfig,
startPollingCheckBuyStatus,
};
}

View File

@ -1,98 +1,35 @@
import classes from "./Home.module.css"; /*
import useUserStore from "@/store/User"; * @LastEditors: John
* @Date: 2024-06-26 15:04:10
* @LastEditTime: 2024-06-28 14:36:37
* @Author: John
*/
import classes from "./Home-m.module.css";
import { cn, copyText, getLevelName, shortenString } from "@/utils"; import { cn, copyText, getLevelName, shortenString } from "@/utils";
import { useWeb3Modal } from "@web3modal/wagmi/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import logo from "@/assets/logo.svg"; import logo from "@/assets/logo.svg";
import nftBg from "@/assets/nft_bg.svg"; import nftBg from "@/assets/nft_bg.svg";
import usdtBg from "@/assets/usdt_bg.svg";
import IconFont from "@/components/iconfont"; import IconFont from "@/components/iconfont";
import { BaseError, useAccount } from "wagmi"; import { PullToRefresh } from "antd-mobile";
import { config } from "@/components/WalletProvider";
import { createSearchParams, useNavigate } from "react-router-dom";
import { Button, Dialog, Empty, PullToRefresh, Toast } from "antd-mobile";
import { loginOut } from "@/utils/wallet"; import { loginOut } from "@/utils/wallet";
import { import useHome from "./Feature/useHome";
api_claim_income, import useMint from "./Feature/useMint";
api_get_homepage_user_data, import useInvitationList from "./Feature/useInvitationList";
api_query_user_invitation_code, function Mobile() {
} from "@/server/api";
import { UserHomeData } from "@/server/module";
import { UrlQueryParamsKey } from "@/constants";
import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus";
import { ToastHandler } from "antd-mobile/es/components/toast";
import { disconnect, getAccount } from "@wagmi/core";
import { PullStatus } from "antd-mobile/es/components/pull-to-refresh";
export default function () {
const { Token, UpdateToken } = useUserStore();
const { open } = useWeb3Modal();
const { t } = useTranslation();
const { address } = useAccount();
const [tabIndex, setTabIndex] = useState(0);
const navigate = useNavigate();
const [userData, setUserData] = useState<UserHomeData>();
const [inviteCode, setInviteCode] = useState("");
const userInviteLink = useMemo(
() =>
`${location.origin}/#/?${UrlQueryParamsKey.INVITE_CODE}=${inviteCode}`,
[inviteCode]
);
const receiveLoadingToast = useRef<ToastHandler>();
const { const {
transcationStatus, getHomeData,
startPollingCheckBuyStatus, statusRecord,
stopPollingCheckBuyStatus, Token,
} = usePollingCheckBuyStatus("NORMAL"); address,
setUserData,
const statusRecord: Record<PullStatus, string> = { userData,
pulling: "Pull down to refresh", tabIndex,
canRelease: "Release to refresh immediately", t,
refreshing: "Loading...", setTabIndex,
complete: "Refresh complete", navigate,
}; userInviteLink,
} = useHome();
useEffect(() => {
if (Token) {
getHomeData();
getInviteCode();
}
return () => {};
}, [Token]);
useEffect(() => {
if (transcationStatus == "success") {
receiveLoadingToast.current?.close();
stopPollingCheckBuyStatus();
Dialog.alert({
content: `${t("领取成功,前往钱包查看")}`,
confirmText: "OK",
});
}
return () => {};
}, [transcationStatus]);
async function getHomeData() {
const { data } = await api_get_homepage_user_data().send({});
setUserData(data?.data);
}
async function getInviteCode() {
const { data } = await api_query_user_invitation_code().send({});
setInviteCode(data?.data.invitationCode || "");
}
useEffect(() => {
console.log("user token:", Token);
return () => {};
}, [Token]);
return ( return (
<>
<PullToRefresh <PullToRefresh
onRefresh={async () => { onRefresh={async () => {
await getHomeData(); await getHomeData();
@ -309,6 +246,42 @@ export default function () {
</div> </div>
</div> </div>
</PullToRefresh> </PullToRefresh>
</>
); );
} }
function Desktop() {
const {
getHomeData,
statusRecord,
Token,
address,
setUserData,
userData,
tabIndex,
t,
setTabIndex,
navigate,
userInviteLink,
} = useHome();
const {
nodeConfig,
num,
setNum,
approveUsdt,
balance,
approveLoadingToast,
costNum,
setApproveUsdt,
buyLoadingToast,
orderInfo,
updateNodeConfig,
startPollingCheckBuyStatus,
} = useMint();
const { columns, list, loadMore, hasMore } = useInvitationList();
return <></>;
}
export default {
Mobile,
Desktop,
};

View File

@ -4,40 +4,12 @@
* @LastEditTime: 2024-06-27 15:33:01 * @LastEditTime: 2024-06-27 15:33:01
* @Author: John * @Author: John
*/ */
import usePagination from "@/hook/usePagination";
import { api_recommended_list } from "@/server/api";
import { RecommendedListItem } from "@/server/module";
import { Empty, InfiniteScroll } from "antd-mobile"; import { Empty, InfiniteScroll } from "antd-mobile";
import DataTable, { TableColumn } from "react-data-table-component"; import DataTable from "react-data-table-component";
import { useTranslation } from "react-i18next"; import useInvitationList from "./Feature/useInvitationList";
export default function () { export default function () {
const { t } = useTranslation(); const { columns, list, loadMore, hasMore } = useInvitationList();
const columns: TableColumn<RecommendedListItem>[] = [
{
name: t("地址"),
selector: (row) => row.walletAddress,
grow: 9,
},
{
name: "Node",
selector: (row) => row.nodeNumber,
grow: 1,
// @ts-ignore
right: "true",
},
];
const { list, hasMore, loadMore } = usePagination<RecommendedListItem>({
service({ pageNum, pageSize }) {
return new Promise(async (reslove) => {
const { data } = await api_recommended_list().send({
queryParams: { pageNum, pageSize },
});
reslove(data?.data.records || []);
});
},
});
return ( return (
<> <>

View File

@ -1,110 +1,36 @@
/* /*
* @LastEditors: John * @LastEditors: John
* @Date: 2024-06-18 15:28:03 * @Date: 2024-06-18 15:28:03
* @LastEditTime: 2024-06-27 17:17:09 * @LastEditTime: 2024-06-28 14:33:02
* @Author: John * @Author: John
*/ */
import { cn, filterAddressBeforeZero, filterAmountBeforeZero } from "@/utils"; import { cn, filterAddressBeforeZero, filterAmountBeforeZero } from "@/utils";
import classes from "./Mint.module.css"; import classes from "./Mint-m.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";
import Space from "antd-mobile/es/components/space"; import Space from "antd-mobile/es/components/space";
import { useTranslation } from "react-i18next"; import { api_node_order } from "@/server/api";
import { useEffect, useMemo, useRef, useState } from "react"; import { authorizedU, getApproveUsdt, payByContract } from "@/contract/utils";
import { import { Stepper, Toast } from "antd-mobile";
api_get_homepage_user_data,
api_get_nft_configuration_data,
api_node_order,
api_users_cancel_orders,
} from "@/server/api";
import {
NftConfigurationData,
NftOrder,
NodeOrder,
UserHomeData,
} from "@/server/module";
import {
authorizedU,
getApproveUsdt,
getBalance,
payByContract,
} from "@/contract/utils";
import { Dialog, Modal, Stepper, Toast } from "antd-mobile";
import useUserStore from "@/store/User";
import usePollingCheckBuyStatus from "@/hook/usePollingCheckBuyStatus";
import { ToastHandler } from "antd-mobile/es/components/toast";
import { BaseError } from "wagmi"; import { BaseError } from "wagmi";
import { useNavigate } from "react-router-dom";
import { toWei } from "web3-utils"; import { toWei } from "web3-utils";
import useMint from "./Feature/useMint";
export default function () { export default function () {
const { t } = useTranslation();
const { Token } = useUserStore();
const [nodeConfig, setNodeConfig] = useState<UserHomeData>();
const [approveUsdt, setApproveUsdt] = useState<bigint>(0n);
const [balance, setBalance] = useState<bigint>(0n);
const orderInfo = useRef<NodeOrder>();
const navigate = useNavigate();
const [num, setNum] = useState(1);
const buyLoadingToast = useRef<ToastHandler>();
const approveLoadingToast = useRef<ToastHandler>();
const { const {
transcationStatus, nodeConfig,
num,
setNum,
approveUsdt,
t,
balance,
approveLoadingToast,
costNum,
setApproveUsdt,
buyLoadingToast,
orderInfo,
updateNodeConfig,
startPollingCheckBuyStatus, startPollingCheckBuyStatus,
stopPollingCheckBuyStatus, } = useMint();
} = usePollingCheckBuyStatus("NORMAL");
const costNum = useMemo(
() =>
BigInt(
toWei(`${parseFloat(`${nodeConfig?.nodePrice || "0"}`) * num}`, "ether")
),
[nodeConfig?.nodePrice, num]
);
useEffect(() => {
updateNodeConfig();
return () => {};
}, []);
useEffect(() => {
(async () => {
Toast.show({ icon: "loading", content: t("正在获取已授权金额") });
setBalance(await getBalance());
setApproveUsdt(await getApproveUsdt());
Toast.clear();
})();
return () => {};
}, [Token]);
async function updateNodeConfig() {
const { data } = await api_get_homepage_user_data().send({});
setNodeConfig(data?.data);
}
useEffect(() => {
if (transcationStatus == "success") {
buyLoadingToast.current?.close();
stopPollingCheckBuyStatus();
Dialog.alert({
content: `${t(
"Buy successful. Please return to the homepage to view."
)}`,
confirmText: "OK",
onConfirm() {
navigate("/");
},
});
}
return () => {};
}, [transcationStatus]);
useEffect(() => {
return () => {};
}, []);
return ( return (
<> <>
<div className={cn(classes.Mint, classes.container)}> <div className={cn(classes.Mint, classes.container)}>

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-27 11:40:59 * @LastEditTime: 2024-06-28 15:32:57
* @Author: John * @Author: John
*/ */
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from "clsx";