From eada4f8822fc6bd0b1aa6bc6cb1a78ddf4c75cb4 Mon Sep 17 00:00:00 2001 From: yyy9608 Date: Wed, 3 Jan 2024 18:00:26 +0800 Subject: [PATCH] commit --- .env | 1 + package.json | 5 +- public/index.html | 3 +- src/api/index.ts | 80 ++++++- src/api/service.ts | 10 +- src/assets/iconfont/iconfont.css | 26 ++- src/assets/iconfont/iconfont.js | 2 +- src/assets/iconfont/iconfont.json | 35 ++++ src/assets/iconfont/iconfont.ttf | Bin 4000 -> 4812 bytes src/assets/iconfont/iconfont.woff | Bin 2760 -> 3248 bytes src/assets/iconfont/iconfont.woff2 | Bin 2216 -> 2628 bytes src/assets/share.png | Bin 0 -> 11448 bytes src/components/CoinPicker.tsx | 46 +++++ src/components/Modal.tsx | 51 +++++ src/components/ProductItem.tsx | 23 ++- src/hooks/useWs.ts | 37 ++++ src/index.tsx | 6 +- src/pages/home/index.tsx | 42 +++- src/pages/personal/AccountAssetsCard.tsx | 13 +- src/pages/personal/index.tsx | 42 ++-- src/pages/product/index.tsx | 5 +- src/pages/recharge/index.tsx | 79 ++++--- src/pages/record/index.tsx | 129 +++--------- src/pages/share/index.tsx | 209 ++++++++++++++++--- src/pages/team/index.tsx | 252 +++++++++++++++++++++++ src/pages/withdraw/index.tsx | 64 ++++-- src/router/layout/index.tsx | 131 +++++++++++- src/router/layout/ui.tsx | 123 +++++++++++ src/router/routes.tsx | 10 +- src/store/index.ts | 24 ++- src/styles/components.scss | 19 +- src/styles/layout.scss | 27 ++- src/styles/personal.scss | 89 +++++++- src/styles/recharge.scss | 3 + src/styles/share.scss | 20 +- src/styles/theme.scss | 2 +- src/types/api.d.ts | 14 +- src/types/store.d.ts | 49 ++++- src/utils/copy.ts | 2 +- src/utils/index.ts | 33 ++- src/utils/sign/sign.ts | 1 + yarn.lock | 109 +++++++++- 42 files changed, 1538 insertions(+), 278 deletions(-) create mode 100644 src/assets/share.png create mode 100644 src/components/CoinPicker.tsx create mode 100644 src/components/Modal.tsx create mode 100644 src/hooks/useWs.ts create mode 100644 src/pages/team/index.tsx create mode 100644 src/router/layout/ui.tsx diff --git a/.env b/.env index 0c2dcd1..cd3cba2 100644 --- a/.env +++ b/.env @@ -1,5 +1,6 @@ SKIP_PREFLIGHT_CHECK=true GENERATE_SOURCEMAP=false REACT_APP_BASE_URL='http://14.29.101.215:30307' +REACT_APP_WS_URL='ws://14.29.101.215:30307' REACT_APP_SHARE_LINK='http://14.29.101.215:30305/#/' REACT_APP_SIGN_KEY='9527nft9527_@fsdgfsx' \ No newline at end of file diff --git a/package.json b/package.json index 283f49c..48fe6dc 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "postcss-normalize": "^10.0.1", "postcss-preset-env": "^7.0.1", "prompts": "^2.4.2", + "qrcode": "^1.5.3", "react": "^18.2.0", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", @@ -70,7 +71,8 @@ "webpack": "^5.64.4", "webpack-dev-server": "^4.6.0", "webpack-manifest-plugin": "^4.0.2", - "workbox-webpack-plugin": "^6.4.1" + "workbox-webpack-plugin": "^6.4.1", + "ws": "^8.16.0" }, "scripts": { "start": "node scripts/start.js", @@ -97,6 +99,7 @@ }, "devDependencies": { "@types/lodash": "^4.14.202", + "@types/qrcode": "^1.5.5", "postcss-px-to-viewport": "^1.1.1", "sass": "^1.69.5", "sass-resources-loader": "^2.2.5" diff --git a/public/index.html b/public/index.html index 74c8dd8..23fd73e 100644 --- a/public/index.html +++ b/public/index.html @@ -4,7 +4,8 @@ - + + diff --git a/src/api/index.ts b/src/api/index.ts index cf245a1..c5118dd 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,8 +1,9 @@ import { PerformSignin, PerformSNonce } from "~/types"; +import { HTTPHistoryParams, HTTPWithdrawParams } from "~/types/api"; import request from "./service"; /** - * @description 获取随机数 + * @description random */ export const getNonce = (query: PerformSNonce) => request({ @@ -11,28 +12,87 @@ export const getNonce = (query: PerformSNonce) => }); /** - * @description 签名 + * @description Sign */ export const performSignin = (query: PerformSignin) => request({ url: "/v1/signin", data: query }); /** - * @description 上传图片 + * @description Upload avatar */ -export const uploadImage = (file: File) => { +export const upload_image = (file: File) => { const body = new FormData(); - console.log(body); - body.append("img", file); - return request({ url: "/v1/uploadImg", data: body }) + return request({ url: "/v1/uploadImg", data: body }); }; /** - * @description 用户信息 + * @description 用戶信息 */ -export const accountInfo = () => request({ url: '/v1/account' }) +export const accountInfo = () => request({ url: "/v1/account" }); /** * @description coin list */ -export const coin_list = () => request({ url: '/v1/tokenList' }) +export const coin_list = () => request({ url: "/v1/tokenList" }); + +/** + * @description market list + * @param {type} 1.拍賣 2.售賣 + */ +export const market_list = (type: 1 | 2) => + request({ url: "/v1/market", data: { type } }); + +/** + * @description 重命名 + * @param {name} 昵稱 + */ +export const reset_name = (name: string) => + request({ url: "/v1/rename", data: { name } }); + +/** + * @description 開放藝術頁面 + * @param {type} 1.展示 2.隱藏 + */ +export const open_page = (type: 1 | 2) => + request({ url: "/v1/assetsShow", data: { show: type } }); + +/** + * @description 我的NFT 我喜歡的 + * @param {type} 1.我的 2.我喜歡的 + */ +export const personal_nft = (type: number) => + request({ url: "/v1/myNft", data: { type: type } }); + +/** + * @description 體現 + * @param {type} 1.我的 2.我喜歡的 + */ +export const user_withdraw = (query: HTTPWithdrawParams) => + request({ url: "/v1/withdraw", data: query }); + +/** + * @description 歷史記錄 + * @param {type} 0.體現 1.體現 2.收益 + * @param {page_size} 數量 + * @param {page} 頁數 + */ +export const history_record = (query: HTTPHistoryParams) => + request({ url: "/v1/hisory", data: query }); + +/** + * @description 團隊信息 + */ +export const team_info = () => request({ url: "/v1/invitiInfo" }); + +/** + * @description 我的邀請 + */ +export const my_invite = (query: { page: number; page_size: number }) => + request({ url: "/v1/invitiDetails", data: query }); + +/** + * @description 綁定推薦人 + */ +export const bind_rmd = (address: string) => + request({ url: "/v1/binding", data: { address } }); diff --git a/src/api/service.ts b/src/api/service.ts index 538c1e8..702843f 100644 --- a/src/api/service.ts +++ b/src/api/service.ts @@ -1,5 +1,5 @@ import axiosConfig from "./axios_config"; -import axios from "axios"; +import axios, { AxiosResponse } from "axios"; import signGenerator from "../utils/sign/sign"; import { Toast } from "react-vant"; import sortParam from "../utils/sign/sort"; @@ -10,8 +10,6 @@ const service = axios.create(axiosConfig); // 请求拦截 service.interceptors.request.use( (config) => { - console.log(config); - (config.headers as any).token = store.state.token; if (!config.data) config.data = {}; let ps = config.params ? sortParam(config.params) : ""; @@ -24,9 +22,9 @@ service.interceptors.request.use( let sign = signGenerator(signData); (config.headers as any).sign = sign; (config.headers as any).timestamp = timestamp; - - if(config.data instanceof FormData) return config - + + if (config.data instanceof FormData) return config; + config.data = JSON.stringify(config.data); return config; }, diff --git a/src/assets/iconfont/iconfont.css b/src/assets/iconfont/iconfont.css index b65d74d..8986fe5 100644 --- a/src/assets/iconfont/iconfont.css +++ b/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4379626 */ - src: url('iconfont.woff2?t=1703840169958') format('woff2'), - url('iconfont.woff?t=1703840169958') format('woff'), - url('iconfont.ttf?t=1703840169958') format('truetype'); + src: url('iconfont.woff2?t=1704271682012') format('woff2'), + url('iconfont.woff?t=1704271682012') format('woff'), + url('iconfont.ttf?t=1704271682012') format('truetype'); } .iconfont { @@ -13,6 +13,26 @@ -moz-osx-font-smoothing: grayscale; } +.icon-close:before { + content: "\e6a7"; +} + +.icon-download:before { + content: "\e60d"; +} + +.icon-fuzhi_copy:before { + content: "\e618"; +} + +.icon-chenggong:before { + content: "\e63c"; +} + +.icon-bianji:before { + content: "\e67f"; +} + .icon-datijilu:before { content: "\e606"; } diff --git a/src/assets/iconfont/iconfont.js b/src/assets/iconfont/iconfont.js index 1710846..76b33cd 100644 --- a/src/assets/iconfont/iconfont.js +++ b/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4379626='',function(e){var t=(t=document.getElementsByTagName("script"))[t.length-1],c=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var l,o,i,n,s,a=function(t,c){c.parentNode.insertBefore(t,c)};if(c&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}l=function(){var t,c=document.createElement("div");c.innerHTML=e._iconfont_svg_string_4379626,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(t=document.body).firstChild?a(c,t.firstChild):t.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),l()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(i=l,n=e.document,s=!1,h(),n.onreadystatechange=function(){"complete"==n.readyState&&(n.onreadystatechange=null,d())})}function d(){s||(s=!0,i())}function h(){try{n.documentElement.doScroll("left")}catch(t){return void setTimeout(h,50)}d()}}(window); \ No newline at end of file +window._iconfont_svg_string_4379626='',function(l){var t=(t=document.getElementsByTagName("script"))[t.length-1],c=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var o,i,e,a,s,n=function(t,c){c.parentNode.insertBefore(t,c)};if(c&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}o=function(){var t,c=document.createElement("div");c.innerHTML=l._iconfont_svg_string_4379626,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(t=document.body).firstChild?n(c,t.firstChild):t.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(e=o,a=l.document,s=!1,d(),a.onreadystatechange=function(){"complete"==a.readyState&&(a.onreadystatechange=null,h())})}function h(){s||(s=!0,e())}function d(){try{a.documentElement.doScroll("left")}catch(t){return void setTimeout(d,50)}h()}}(window); \ No newline at end of file diff --git a/src/assets/iconfont/iconfont.json b/src/assets/iconfont/iconfont.json index 232d65f..7d0e83c 100644 --- a/src/assets/iconfont/iconfont.json +++ b/src/assets/iconfont/iconfont.json @@ -5,6 +5,41 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "257406", + "name": "close", + "font_class": "close", + "unicode": "e6a7", + "unicode_decimal": 59047 + }, + { + "icon_id": "598164", + "name": "download", + "font_class": "download", + "unicode": "e60d", + "unicode_decimal": 58893 + }, + { + "icon_id": "23870326", + "name": "复制_copy", + "font_class": "fuzhi_copy", + "unicode": "e618", + "unicode_decimal": 58904 + }, + { + "icon_id": "7408311", + "name": "成功", + "font_class": "chenggong", + "unicode": "e63c", + "unicode_decimal": 58940 + }, + { + "icon_id": "7443382", + "name": "编辑", + "font_class": "bianji", + "unicode": "e67f", + "unicode_decimal": 59007 + }, { "icon_id": "1240492", "name": "答题记录", diff --git a/src/assets/iconfont/iconfont.ttf b/src/assets/iconfont/iconfont.ttf index c64293a1537f4aa88fa6de4b007bd61d8d745ccd..ec6839b8aeeff9c04470fb24dbbc914993091ea6 100644 GIT binary patch delta 1418 zcmaJ=O>A355T4z&pMR4VJ1=(pqojV0?Kr6f_~*Go5pg9{j3RLXR4O60u9MoX{1;qQ z7pXWLN^(F|!5oUjfr>-9AQkmTsOll5RVfmbs6w22XoR%3P!50u7qHFjdudfTbgggR z%2Ed*xt>l+y z{lB!(_aQQIF;Q9$2{`&Anc+m|6RYc*dlm5fiC4F(Rp+XgN$g9V8Z7=uw50a>q5y1Y#(X``oD4|Iu*6LlECEWq(pKzQ*H1EN73 z2OxlV0FN2K+kh_^5RKwx16Tp@RRendHtGnh3cqJiAh1B->jtn?;1>;urgrxm!1AGc z%K#FAp3eYMf?n1Dl7rqI1A->58hnBD2IP6F(j0Bj`}8;V8rxtGOmn7J+NO`h;}kql ztT6%##Nhx=P_;-FN%4BTorDWx}-a&z@@B{sXJmzHTxTB|#57^29je#azwZn=2Sev%$ie9q zr&o30j+2<2?U2{m6{E*$KV`qO<1hh!n8HLjQOqL~3Fn-|nQ9rLrHCa%yqQ?W%X*(! z{nq}o78<7}$vR@9w{2#+W$UxEQ*>WruJLMves|t#?LQ}3Z(C%umBguq-n07qFHD`? zbr4;;e8Kdlowz%LQNKoHGDcj)O~%1osMMP)*5&%iTBV*}wjX=@ol^O5q24%UE0k7h T#bUiyl#Z73wd3WEPk#Sj*>FVU delta 622 zcmZvYKWGzC9LK-Ecb7|(+@(c2*buc6OBFgw3$K@o>8w$RB%a1k6-^yQEaA~?$VeRp&ae8=bB`~CmE_in6rqFY+~2FL>d zmHE2gxcBVi9YBqe>r1uUHwN$4ih$h%(jTge{=#YR%_Zi3CX-b4WZDrnfZ{{nk-=JNr`|XAt!*j-u(jL9#*B6Jj3o|bOx5a@=jg{5bxr49o zf&3>XynVP+I`2VvB!3<~E?pa(`Hh@S2k_<9^qIpwilTO^W3AI3fHGb#Q|AT8r73ek zS1N(6E?nM?y+*GUpC-6xnYs%bUFH`sWIWo@t{&IRx~VsHr(f>xg(Kl?xNq+EZ!CDK z#-@aI|No4NG#qw!smnqR87j;o#elPY(vmd}bqUam|FNYVGln*=pcyr=4fJ>d8$vH9 zur+iu!F1A_3FaAeCxL^}FDFm}`g;i!Q${#~80tben_%7~+)prC;}uF?CS^-rtAd(S qPt;Fq*t%lpP2mk;8F#rGnHYgh7cxY&4VE_OM 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 delta 2269 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 diff --git a/src/assets/share.png b/src/assets/share.png new file mode 100644 index 0000000000000000000000000000000000000000..7403264392cf53fd16b8e5536b2a83a999662d3c GIT binary patch literal 11448 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 LwGUOYW+DFvr_SJ1 literal 0 HcmV?d00001 diff --git a/src/components/CoinPicker.tsx b/src/components/CoinPicker.tsx new file mode 100644 index 0000000..9042ff8 --- /dev/null +++ b/src/components/CoinPicker.tsx @@ -0,0 +1,46 @@ +import { useMemo } from "react"; +import { Picker } from "react-vant"; +import { CoinList } from "~/types/store"; + +interface CoinPickerProps { + setIndex: Function; + list: CoinList[]; + index: number +} + +const CoinPicker = ({ setIndex, list, index }: CoinPickerProps) => { + + const coinList = useMemo(() => list.map(item => item.symbol), [list]); + + return ( + { + setIndex(_index); + }} + placeholder="" + value={coinList[index]} + > + {(_val, _, _action) => ( +
_action.open()} style={{ width: '100%' }}> + { + list[index] && ( +
+
+ +
{list[index].symbol}
+
+
+
+ ) + } +
+ )} +
+ ) +} + +export default CoinPicker; \ No newline at end of file diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..9274971 --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,51 @@ +import { Overlay } from 'react-vant'; +import '~/styles/components.scss' + +interface ModalProps { + buttonText: string; + title: string; + children: JSX.Element; + buttonClick: Function; + visible: boolean; + setVisible: Function; + hiddenCloseIcon?: boolean; + showCancelButton?: boolean; + showCancelButtonText?: string; + showCancelButtonClick?: Function; +} + +const Modal = ( + { title, buttonClick, visible, setVisible, children, buttonText, hiddenCloseIcon, showCancelButton, showCancelButtonText, showCancelButtonClick }: ModalProps +) => { + return ( + +
+
+
+
{title}
+
+ { + !hiddenCloseIcon && ( + setVisible(false)} /> + ) + } +
+
+ {children} +
+
buttonClick()}>{buttonText}
+ { + showCancelButton && +
showCancelButtonClick && showCancelButtonClick()}>{showCancelButtonText || '關閉'}
+ } +
+
+
+ ) +} + +export default Modal \ No newline at end of file diff --git a/src/components/ProductItem.tsx b/src/components/ProductItem.tsx index 131b010..a306d18 100644 --- a/src/components/ProductItem.tsx +++ b/src/components/ProductItem.tsx @@ -1,17 +1,26 @@ import '~/styles/components.scss' +import { MarketNFTData } from '~/types/store' +import { splitAddress, toThousands } from '~/utils' + +interface ProductItemProps { + data: MarketNFTData +} + +const ProductItem = (props: ProductItemProps) => { + + const { data } = props -const ProductItem = () => { return (
- {/* */} -
+ + {/*
*/}
-
卡达鸭
+
{data.name}
-
USDT 10000
+
{data?.symbol} {toThousands(data?.price)}
- -
jackasddf
+ +
{splitAddress(data.sell_name,4)}
diff --git a/src/hooks/useWs.ts b/src/hooks/useWs.ts new file mode 100644 index 0000000..8763471 --- /dev/null +++ b/src/hooks/useWs.ts @@ -0,0 +1,37 @@ +import { useRef } from "react"; +import signGenerator from "~/utils/sign/sign"; + +const useWs = (path: string) => { + const baseUrl = `ws://14.29.101.215:30307/api/v1/${path}`; + // const baseUrl = `ws://192.168.124.52:8083/api/v1/${path}`; + const ws = useRef(null); + + const connect = (token: string) => { + let timestamp = Date.now(); + let signData = { + uri: `/api/v1/${path}`, + timestamp: timestamp, + args: "", + }; + const sign = signGenerator(signData); + ws.current = new WebSocket( + `${baseUrl}?Token=${token}&sign=${sign}×tamp=${timestamp}` + ); + + ws.current.onMessage = (data: any) => { + console.log(data); + }; + }; + + const disconnect = () => { + ws.current && ws.current.close(); + ws.current = null; + }; + + return { + connect, + disconnect, + }; +}; + +export default useWs; diff --git a/src/index.tsx b/src/index.tsx index 525aa1f..66ac3e0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,7 +8,7 @@ const root = ReactDOM.createRoot( ); root.render( - - - + // + + // ); \ No newline at end of file diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 92a5c29..22961c8 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -2,39 +2,60 @@ import '~/styles/home.scss' import ProductItem from '~/components/ProductItem' import { useRouter } from '~/hooks/useRouter' import { Button } from 'react-vant' +import { useEffect, useState } from 'react' +import { market_list } from '~/api' +import { MarketNFTData } from '~/types/store' +import { splitAddress } from '~/utils' +import { observer } from 'mobx-react' const Home = () => { const { push } = useRouter() + const [marketData, setMarketData] = useState([] as MarketNFTData[]) + const [auctionData, setAuctionData] = useState([] as MarketNFTData[]) + + + useEffect(() => { + const getMarketData = async () => { + const res: any = await market_list(2) + res && res.code === 0 && res.data && setMarketData(res.data) + } + const getAuctionData = async () => { + const res: any = await market_list(1) + res && res.code === 0 && res.data && setAuctionData(res.data) + } + getMarketData() + getAuctionData() + }, []) return (
拍卖趋势
{ - Array.from({ length: 5 }).map((_, index) => ( + auctionData.map((item, index) => (
- push('/detail')} /> + push('/detail', { id: item.id })} />
-
生肖唐彩-龙
+
{item.name}
铸造者
- -
Filefast
+ +
{splitAddress(item.cast_name, 4)}
售卖者
- -
Filefast
+ +
{splitAddress(item.sell_name, 4)}
@@ -55,12 +76,15 @@ const Home = () => {
{ - Array.from({ length: 10 }).map((_, index) => ( - + marketData.map((item, index) => ( +
push('/detail', { id: item.id })}> + +
)) }
+
) } diff --git a/src/pages/personal/AccountAssetsCard.tsx b/src/pages/personal/AccountAssetsCard.tsx index 76d7b73..b89c510 100644 --- a/src/pages/personal/AccountAssetsCard.tsx +++ b/src/pages/personal/AccountAssetsCard.tsx @@ -1,15 +1,18 @@ import '~/styles/personal.scss' import { useMemo } from "react"; import { useRouter } from "~/hooks/useRouter"; +import { observer } from 'mobx-react'; +import store from '~/store'; const AccountAssetsCard = () => { const { push } = useRouter(); + const { userInfo } = store.state const balanceList = useMemo(() => [ - { title: 'USDT资产', value: 0, symbol: 'USDT' }, - { title: 'FIL资产', value: 0, symbol: 'FIL' }, - ], []); + { title: 'USDT资产', value: userInfo.balance_Usdt || 0, symbol: 'USDT' }, + { title: 'FIL资产', value: userInfo.balance_fil || 0, symbol: 'FIL' }, + ], [userInfo]); const menu = useMemo(() => [ { title: '充值', icon: require('~/assets/recharge.png'), path: '/recharge' }, @@ -24,7 +27,7 @@ const AccountAssetsCard = () => {
总资产估值
-
1429.32 USDT
+
{userInfo.total} USDT
{ @@ -58,4 +61,4 @@ const AccountAssetsCard = () => { ) }; -export default AccountAssetsCard; \ No newline at end of file +export default observer(AccountAssetsCard); \ No newline at end of file diff --git a/src/pages/personal/index.tsx b/src/pages/personal/index.tsx index b2bd1d9..eab0fc9 100644 --- a/src/pages/personal/index.tsx +++ b/src/pages/personal/index.tsx @@ -1,17 +1,15 @@ +import { observer } from 'mobx-react' import { Divider } from 'react-vant' +import store from '~/store' import '~/styles/personal.scss' import AccountAssetsCard from './AccountAssetsCard' const Personal = () => { + const { userInfo } = store.state + return (
- {/* Toast.info('文件大小不能超过500kb')} - upload={upload} - /> */}
@@ -25,18 +23,24 @@ const Personal = () => {
名称
可用
-
-
- -
-
USDT
-
TetherUS
-
-
-
-
100
-
$429.32
-
+
+ { + userInfo.assets && userInfo.assets.map(item => ( +
+
+ +
+
{item.symbol}
+
{item.name}
+
+
+
+
{item.amount}
+
{item.symbol !== 'USDT' && item.amount_usdt}
+
+
+ )) + }
@@ -44,4 +48,4 @@ const Personal = () => { ) } -export default Personal \ No newline at end of file +export default observer(Personal) \ No newline at end of file diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx index 44f590f..757cadd 100644 --- a/src/pages/product/index.tsx +++ b/src/pages/product/index.tsx @@ -1,5 +1,6 @@ import { Divider } from 'react-vant' import '~/styles/product.scss' +import { MarketNFTData } from '~/types/store' import ProductItem from '../../components/ProductItem' const Product = () => { @@ -18,11 +19,11 @@ const Product = () => {
{ Array.from({ length: 6 }).map((_, index) => ( - + )) }
- +
) } diff --git a/src/pages/recharge/index.tsx b/src/pages/recharge/index.tsx index d59291d..6462e28 100644 --- a/src/pages/recharge/index.tsx +++ b/src/pages/recharge/index.tsx @@ -1,22 +1,25 @@ import '~/styles/recharge.scss' import { observer } from 'mobx-react' import store from '../../store' -import { useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useRouter } from '../../hooks/useRouter' -import { ethers } from 'ethers' +import { BigNumber, ethers } from 'ethers' import { Button, Toast } from 'react-vant' -import { switchNetWork } from '../../utils' +import { switchNetWork, toFixed2 } from '../../utils' import { IERC20__factory } from '../../contract/IERC20__factory' import { toWei } from '../../utils/wei' import BackBar from '~/components/BackBar' +import CoinPicker from '~/components/CoinPicker' const Recharge = () => { - const { coinIndex, coinList, userInfo } = store.state + const { coinList, userInfo, walletAddress, token } = store.state const { push } = useRouter() - const inputRef = useRef(null); + const inputRef = useRef(null) + const [coinIndex, setCoinIndex] = useState(0) const currentCoin = useMemo(() => coinList[coinIndex], [coinIndex, coinList]) - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false) + const [balance, setBalance] = useState({} as { [key: string]: string }) const provider = useMemo(() => { if (window.ethereum) { @@ -75,6 +78,7 @@ const Recharge = () => { const handleExchange = async () => { if (!provider || loading) return; let value = inputRef.current?.value; + if (!value) { Toast.info('請輸入數量'); return; @@ -83,8 +87,10 @@ const Recharge = () => { setLoading(true); try { let isNode = await switchNetWork(); + if (!isNode) return; let data = await handleTransferParams(); + if (data == -1) { // Toast.info('请检查网络是否正常'); setLoading(false); @@ -109,27 +115,55 @@ const Recharge = () => { Toast.fail('充值失敗'); } } catch (error) { + console.log(error); + setLoading(false); } }; + useEffect(() => { + const getBalance = async () => { + if (!provider) return + + try { + + const balance = {} as { [key: string]: string } + const ps = coinList.map((item, index) => { + let _contract = IERC20__factory.connect(item.address, provider); + balance[item.symbol] = '' + return _contract.balanceOf(walletAddress) + }) + const res = await Promise.all(ps) + Array.isArray(res) && res.forEach((item, index) => { + BigNumber.isBigNumber(item) && (balance[coinList[index].symbol] = toFixed2(ethers.utils.formatUnits(item, 18), 4)) + }) + + setBalance(balance) + } catch (error) { + + } + } + + token && getBalance() + + }, [coinList, provider, token, walletAddress]) + + useEffect(() => { + inputRef.current && (inputRef.current.value = '') + }, [coinIndex]) return (
-
push('/choose')}> -
- { - currentCoin && - } -
{currentCoin && currentCoin.symbol}
-
-
- -
+
+
-
+
push('/record')}>
@@ -142,14 +176,11 @@ const Recharge = () => {
數量
-
- { - currentCoin && - } -
{currentCoin && currentCoin.symbol}
- {/*
最大
*/} -
+
{ + balance[currentCoin.symbol] && (inputRef.current!.value = balance[currentCoin.symbol]) + }}>ALL
+
可用餘額:{currentCoin.symbol ? balance[currentCoin.symbol] : 0}
diff --git a/src/pages/record/index.tsx b/src/pages/record/index.tsx index d22e441..6f08946 100644 --- a/src/pages/record/index.tsx +++ b/src/pages/record/index.tsx @@ -8,14 +8,15 @@ import BackBar from "~/components/BackBar"; import { Cell, Empty, List, Tabs } from "react-vant"; import { debounce } from 'lodash'; import '~/styles/personal.scss'; +import { history_record } from "~/api"; const Record = () => { const { token } = store.state; + const { push } = useRouter() const recordTabs = useMemo(() => ['充值', '提现', '收益'], []); const [recordIndex, setRecordIndex] = useState(0); const { copyVal } = useCopyLink(); - const { push } = useRouter(); const [query, setQuery] = useState([ { page: 1, page_size: 20 }, { page: 1, page_size: 20 }, @@ -30,8 +31,7 @@ const Record = () => { const getAssetsRecord = debounce(async () => { - // let res: any = await assets_record({ type: recordIndex + 1, ...query[recordIndex] }); - let res: any = {}; + let res: any = await history_record({ type: recordIndex + 1, ...query[recordIndex] }); if (res.code === 0) { if (res.data.length < 20) { if (assetsRecord[recordIndex].length <= 0) { @@ -62,17 +62,14 @@ const Record = () => { , , , - , - , - ]; return el[index]; }; useEffect(() => { - // token && assetsRecord[recordIndex].length <= 0 && getAssetsRecord(); - // !token && push('/', null, true); - // !token && setFinished([true, true, true, true, true, true]); + token && assetsRecord[recordIndex].length <= 0 && getAssetsRecord(); + !token && push('/', null, true); + !token && setFinished([true, true, true, true, true, true]); }, [recordIndex, token]); @@ -124,30 +121,32 @@ const RechargeRecord = ( return ( getData()} errorText="请求失败,点击重新加载" offset={10}> { - list.map(item => ( - + list.map((item) => ( +
-
{item.status === -3 ? '激活赠送' : '充值'}
-
{getTime(item.time * 1000)}
-
- 交易哈希:{splitAddress(item.order, 10)} - copy && copy(item.order)}> +
{item.name}
+
{getTime(item.time * 1000)}
+
+ 交易哈希:{splitAddress(item.tx_hash, 10)} + { + item.tx_hash && ( + copy && copy(item.tx_hash)}> + ) + }
-
+{item.amount} {item.symbol}
-
= 0 ? 'blue-color' : 'red-color'}`} - > - {item.status !== -3 && (item.status === 3 ? '完成' : item.type >= 0 ? '确认中' : '失败')} +
+{item.amount} {item.symbol}
+
+ {item.status === 1 ? '完成' : '確認中'}
)) } - + ) } @@ -159,7 +158,7 @@ const WithdrawRecord = ({ list, finished, getData, copy }: ChildProps) => { getData()} errorText="请求失败,点击重新加载" offset={10}> { list.map((item, index) => ( - +
提现
@@ -196,7 +195,7 @@ const TransferRecord = ({ list, finished, getData }: ChildProps) => { getData()} errorText="请求失败,点击重新加载" offset={10}> { list.map((item, index) => ( - +
划转 {item.name}
@@ -218,86 +217,4 @@ const TransferRecord = ({ list, finished, getData }: ChildProps) => { ) } -const OrderRecord = ( - { list, finished, getData }: ChildProps -) => { - - if (list.length <= 0 || Object.keys(list).length <= 0) return ; - - return ( - <> - getData()} errorText="请求失败,点击重新加载" offset={10}> - { - list.map(item => ( - -
-
ID {item.order}
-
{getTime(item.time * 1000)}
-
-
-
{item.type === 1 ? '主单盈亏' : '对冲单盈亏'}
-
= 0 ? "green-color" : 'red-color'}>{Number(item.amount) >= 0 && '+'}{item.amount} {item.symbol}
-
-
-
保险赔付
-
= 0 ? "green-color" : 'red-color'}>{Number(item.rebate_amount) >= 0 && '+'}{item.rebate_amount} {item.symbol}
-
-
-
结算费
-
{item.settle_fee} {item.symbol_fee}
-
-
- )) - } -
- - ) -} - -const ProfitPositionRecord = ({ list, getData, finished }: ChildProps) => { - - if (list.length <= 0 || Object.keys(list).length <= 0) return ; - - return ( - getData()} errorText="请求失败,点击重新加载" offset={10}> - { - list.map(item => ( - -
-
ID {item.order}
-
{getTime(item.time * 1000)}
-
-
-
{item.name}
-
{Number(item.amount) >= 0 && '+'}{item.amount} {item.symbol}
-
-
- )) - } -
- ) -} - -const MonthlyFeeRecord = ({ list, finished, getData }: ChildProps) => { - - if (list.length <= 0 || Object.keys(list).length <= 0) return ; - - return ( - getData()} errorText="请求失败,点击重新加载" offset={10}> - { - list.map(item => ( - -
-
{item.name}
-
{Number(item.amount) >= 0 && '+'}{item.amount} {item.symbol}
-
-
{getTime(item.time * 1000)}
-
- )) - } -
- ) -} - - export default observer(Record); \ No newline at end of file diff --git a/src/pages/share/index.tsx b/src/pages/share/index.tsx index f9c35ea..77bd4d2 100644 --- a/src/pages/share/index.tsx +++ b/src/pages/share/index.tsx @@ -1,32 +1,166 @@ -import { Tabs } from 'react-vant' import '~/styles/share.scss' +import { Popup, Tabs, Toast } from 'react-vant' import ProductItem from '~/components/ProductItem' +import { MarketNFTData } from '~/types/store' +import store from '~/store' +import { observer } from 'mobx-react' +import { toFixed2 } from '~/utils' +import { useEffect, useMemo, useRef, useState } from 'react' +import { open_page, personal_nft, reset_name, upload_image } from '~/api' const Share = () => { + + const { userInfo, token } = store.state + const [visible, setVisible] = useState(false) + const inputRef = useRef(null) + const nameRef = useRef(null) + const [edit, setEdit] = useState(false) + const prevent = useRef(false) //阻止重複點擊 + const fileRef = useRef(null as any) + const [tempUrl, setTempUrl] = useState(null as any) + const [nftList, setNftList] = useState([ + [], //我的 + [],//我喜歡的 + ] as MarketNFTData[][]) + const tabs = useMemo(() => ['我的NFT', '我喜歡的NFT'], []) + + const avatarmenuClick = (event: any) => { + const dataType = event.target.dataset.type; + + switch (dataType) { + case "photo": //相册 + inputRef.current?.click() + break + case "save": //保存 + !fileRef.current && Toast.info('請選擇圖片') + fileRef.current && uploader(fileRef.current) + break + case "cancel": //取消 + closePopup() + break + } + } + + const uploader = async (file: File | null) => { + if (!file) return + setVisible(false) + const res: any = await upload_image(file) + res.code === 0 && await store.getUserInfo() + res.code === 0 && Toast.success('保存成功') + closePopup() + } + + const saveName = async () => { + if (nameRef.current?.value === userInfo.name) return + const res: any = await reset_name(nameRef.current!.value) + res.code === 0 && store.getUserInfo() + setEdit(false) + } + + const openPages = async () => { + if (prevent.current) return + prevent.current = true + const res: any = await open_page(userInfo.show === 1 ? 2 : 1) + res.code === 0 && store.getUserInfo() + res.code === 0 && Toast.success({ + message: userInfo.show === 1 ? '已隐藏' : '已开放', + forbidClick: true + }) + prevent.current = false + } + + const onChangeImage = (event: React.ChangeEvent) => { + let file = event.target.files ? event.target.files[0] : null + if (!file) return + + fileRef.current = file + const reader = new FileReader() + reader.onloadend = function () { + const base64String = reader.result + setTempUrl(base64String) + } + reader.readAsDataURL(file) + inputRef.current!.value = '' + } + + const closePopup = () => { + setVisible(false) + setTempUrl('') + fileRef.current = null + inputRef.current!.value = '' + } + + useEffect(() => { + const getData = async () => { + const res: any = await personal_nft(1) + if (res && res.code === 0) { + nftList[0] = res.data + setNftList([...nftList]) + } + } + + token && getData() + }, [token]) + return ( -
+
- + { + setVisible(true) + }} />
-
IamjackRider
+ { + token ? ( +
+ { + edit ? ( + { + setTimeout(() => { + setEdit(false) + }, 100) + }} /> + ) : ( +
{userInfo.name}
+ ) + } +
+ { + edit ? ( + + ) : ( + { + setEdit(true) + setTimeout(() => { + nameRef.current!.value = userInfo.name + nameRef.current!.focus() + }, 20) + }} /> + ) + } +
+
+ ) : ( +
未登录
+ ) + }
-
开放我的资产
+
{userInfo.show === 2 ? '开放我的艺术页面' : '隐藏我的艺术页面'}
-
5
+
{userInfo.sell}
售卖作品
-
15
+
{userInfo.auction}
拍卖作品
-
255 FIL
+
{toFixed2(userInfo.income, 2)} FIL
收入
@@ -40,32 +174,45 @@ const Share = () => { animated swipeable > - - 我的NFT - 25 -
} - titleClass='fz-wb-550' - > -
- {Array.from({ length: 10 }).map((_, index) => )} -
- - - 我喜欢的NFT - 999+ -
} - titleClass='fz-wb-550' - > -
- {Array.from({ length: 10 }).map((_, index) => )} -
- + { + tabs.map((item, index) => ( + + {item} + {nftList[index].length} +
} + titleClass='fz-wb-550' + > +
+ {nftList[index].map((item, index) => )} +
+ + )) + } +
+ + +
+
+
从手机相册选择
+
保存图片
+
+
+
取消
+
+
) } -export default Share \ No newline at end of file +export default observer(Share) \ No newline at end of file diff --git a/src/pages/team/index.tsx b/src/pages/team/index.tsx new file mode 100644 index 0000000..a4b1927 --- /dev/null +++ b/src/pages/team/index.tsx @@ -0,0 +1,252 @@ +import '~/styles/personal.scss' +import { Button, Divider, List, Overlay, Swiper, SwiperInstance, Toast } from "react-vant" +import BackBar from "~/components/BackBar" +import { useEffect, useRef, useState } from 'react' +import { getTime, splitAddress } from '~/utils' +import { observer } from 'mobx-react' +import store from '~/store' +import { my_invite, team_info } from '~/api' +import useCopyLink from '~/hooks/useCopy' +import { InviteRecordData } from '~/types/store' +import { useRouter } from '~/hooks/useRouter' +import { copy } from '~/utils/copy' +import QRCode from 'qrcode' + +const Team = () => { + + const { push } = useRouter() + const { token } = store.state + const { copyVal } = useCopyLink() + const [address, setAddress] = useState('') + const [inviteList, setInviteList] = useState([] as InviteRecordData[]) + const [finished, setFinished] = useState(true); + const [visible, setVisible] = useState(false); + const [cardState, setCardState] = useState([ + [ + { title: '我的推薦人', value: '000000000', id: 1 }, + { title: '級別獎勵', value: '10% 5%', id: 2 }, + ], + [ + { title: '獎勵金額', value: '0U', id: 4 }, + { title: '直推人數', value: '0人', id: 6 }, + { title: '閒推人數', value: '0人', id: 5 }, + ], + ]) + + const query = useRef({ page: 1, page_size: 20 }) + + const getInviteData = async () => { + let res: any = await my_invite(query.current); + if (res && res.code === 0 && res.data) { + if (res.data.length < 20) { + if (inviteList.length <= 0) { + setInviteList(res.data) + } else { + setInviteList([...inviteList, ...res.data]) + } + setFinished(true) + return + } + query.current.page = query.current.page + 1 + if (inviteList.length <= 0) { + setInviteList(res.data) + } else { + setInviteList([...inviteList, ...res.data]) + }; + setFinished(false) + } + } + + + useEffect(() => { + const getData = async () => { + const res: any = await team_info() + if (res && res.code === 0) { + cardState[0][0].value = res.data.inviti_address || '000000000000000' + cardState[1][0].value = res.data.award ? res.data.award + "U" : "0U" + cardState[1][1].value = res.data.direct_count ? res.data.direct_count + '人' : '0人' + cardState[1][2].value = res.data.indirect_count ? res.data.indirect_count + '人' : '0人' + setCardState([...cardState]) + setAddress(res.data.address) + } + console.log(res); + } + + token && getData() + token && getInviteData(); + !token && push('/', null, true) + }, [token]) + + return ( +
+ + +
+
邀请好友,挣奖励
+
+
最高可得
+
10%
+
+
+ +
+ { + cardState.map((v, index) => ( +
+ { + v.map(item => ( +
+
{item.title}
+
+ {item.id === 1 ? splitAddress(item.value) : item.value} + { + item.id === 1 && ( + copyVal(item.value)}> + ) + } +
+
+ )) + } +
+ )) + } +
+ +
+
我的邀请
+
+ +
+
+
地址
+
+
時間
+
+ +
+ { + inviteList.map((item, index) => ( +
+
{splitAddress(item.address)}
+
+
{getTime(item.time * 1000)}
+
+ )) + } +
+
+
+ { + visible && + } +
+ ) +} + +const ShareModal = ( + { visible, setVisible, address }: { visible: boolean, setVisible: Function, address: string } +) => { + + const [swiperIndex, setSwiperIndex] = useState(0) + const swiperRef = useRef(null) + const [qrcodeUri, setQrcodeUri] = useState('') + + const handleSwiper = () => { + if (swiperIndex === 0) { + swiperRef.current?.swipeNext() + } else { + swiperRef.current?.swipePrev() + } + } + + const downloadQrcode = () => { + if (!qrcodeUri) return + const link = document.createElement('a') + link.href = qrcodeUri + link.download = `${Date.now()}.png` + link.click() + link.remove() + } + + useEffect(() => { + copy(process.env.REACT_APP_SHARE_LINK + address) + QRCode.toDataURL(process.env.REACT_APP_SHARE_LINK + address, (err, url) => { + if (url) { + setQrcodeUri(url) + } + }) + }, []) + + return ( + +
+
+ setVisible(false)}> +
+ <>} + onChange={(index) => setSwiperIndex(index)} + initialSwipe={swiperIndex} + touchable={false} + > + +
+
+
+ +
+
+
+ {splitAddress(process.env.REACT_APP_SHARE_LINK + address, 14)} + { + copy(process.env.REACT_APP_SHARE_LINK + address, () => { + Toast.success('复制成功') + }) + }}> +
+
成功複製分享碼
+
轉發給好友並在錢包裏打開完成綁定
+
+
+ +
+
+ +
+
+ 截圖或下載圖片 + +
+
掃描綁定關係
+
在好友錢包裡掃一掃完成綁定
+
+
+
+ +
+
+
+
+ {swiperIndex === 0 ? '切換二維碼' : '切換分享鏈接'} +
+ +
+
+
+
+
+ ) +} + + +export default observer(Team) \ No newline at end of file diff --git a/src/pages/withdraw/index.tsx b/src/pages/withdraw/index.tsx index 60fc316..3291d55 100644 --- a/src/pages/withdraw/index.tsx +++ b/src/pages/withdraw/index.tsx @@ -3,33 +3,59 @@ import { observer } from 'mobx-react' import store from '../../store' import { useMemo, useRef, useState } from 'react' import { useRouter } from '../../hooks/useRouter' -import { Button } from 'react-vant' +import { Button, Toast } from 'react-vant' import BackBar from '~/components/BackBar' +import CoinPicker from '~/components/CoinPicker' +import { user_withdraw } from '~/api' +import { getChainId } from '~/utils' const Recharge = () => { - const { coinIndex, coinList } = store.state + const { coinList, walletAddress } = store.state + const [coinIndex, setCoinIndex] = useState(0) const { push } = useRouter() const inputRef = useRef(null); const currentCoin = useMemo(() => coinList[coinIndex], [coinIndex, coinList]) const [loading, setLoading] = useState(false); + const withdraw = async () => { + const amount = inputRef.current?.value + const balance = currentCoin.balance + if (!balance) return + if (!amount) return Toast.fail("請輸入數量") + if (Number(balance) < Number(amount)) return Toast.fail("餘額不足") + setLoading(true) + try { + const chainId = await getChainId() + let res: any = await user_withdraw({ + symbol: currentCoin.symbol, + chainId: chainId || -1, + amount + }); + if (res.code === 0 || res.code === 101) { + res.code === 0 && Toast.success('提交成功'); + setTimeout(() => { + push('/record', { index: 1 }, true) + }, 1000) + }; + } catch (error) { + setLoading(false) + } + }; + + return (
-
push('/choose')}> -
- { - currentCoin && - } -
{currentCoin && currentCoin.symbol}
-
-
- -
+
+
-
+
push('/record', { index: 1 })}>
@@ -38,7 +64,7 @@ const Recharge = () => {
提现地址
-
0xasdasd;lasdljaslkdjklasdjlkasjdlk
+
{walletAddress}
@@ -51,22 +77,22 @@ const Recharge = () => {
数量
- +
-
FIL
+
{currentCoin && currentCoin.symbol}
最大
-
可用余额:0 USDT
+
可用余额:{currentCoin ? currentCoin.balance : 0} {currentCoin && currentCoin.symbol}
手续费
-
0.1000 FIL
+
{currentCoin ? currentCoin.withdraw_fee : 0} {currentCoin && currentCoin.symbol}
- +
diff --git a/src/router/layout/index.tsx b/src/router/layout/index.tsx index cd0d8c0..2a08142 100644 --- a/src/router/layout/index.tsx +++ b/src/router/layout/index.tsx @@ -1,17 +1,93 @@ import '~/styles/layout.scss' -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useRouter } from '~/hooks/useRouter'; import Notify from './Notify'; import RenderRouter from './RenderRouter'; -import { tabbarData } from '../routes'; +import routes, { tabbarData } from '../routes'; import Navbar from './Navbar'; import Tabbar from './Tabbar'; +import { observer } from 'mobx-react'; +import store from '~/store'; +import useWs from '~/hooks/useWs'; +import { ethers } from 'ethers'; +import { bind_rmd } from '~/api'; +import { AlreadyBind, BindRmd, BindSuccess, DefaultBind, UnLogin, VaildLink } from './ui'; const LayoutRouter = () => { const { location, push } = useRouter() + const messageWs = useWs('getMessage') + const { token, walletAddress, userInfo, visibleUnLogin } = store.state const [visible, setVisible] = useState(false) + const [visibleVaildLink, setVisibleVaildLink] = useState(false) //无效的链接 + const [visibleAlreadyBind, setVisibleAlreadyBind] = useState(false) //已绑定 + const [visibleBindRmd, setVisibleBindRmd] = useState(false) //直接绑定 + const [shareAddress, setShareAddress] = useState('') + const [visibleBindSuccess, setVisibleBindSuccess] = useState(false) //綁定成功 + const [visibleDefault, setVisibleDefault] = useState(false) //默認綁定 + + + const bindRecommend = async () => { + const res: any = await bind_rmd(shareAddress) + if (res.code === 0) { + setVisibleBindRmd(false) + store.getUserInfo() + setTimeout(() => { + push('/', null, true) + setVisibleBindSuccess(true) + }, 200) + } + } + + useEffect(() => { + token && store.getUserInfo() + token && store.getCoinList() + !token && store.resetCoinList() + !token && store.resetUserInfo() + // token && messageWs.connect(token) + // !token && messageWs.disconnect() + }, [token]) + + useEffect(() => { + const isModal = async () => { + let user = userInfo + let isRouter = routes.find(v => v.path === location.pathname) + const address = location.pathname.substring(1, location.pathname.length) + + if (user.is_bound) return + if (isRouter) return + + if (Object.keys(user).length <= 0) { + const res: any = await store.getUserInfo() + if (res) { + user = res + } + } + if (!ethers.utils.isAddress(address)) { + // 无效的分享链接 + setVisibleVaildLink(true) + return + } + // 地址相同 + if (address.toLocaleLowerCase() === walletAddress.toLocaleLowerCase()) return + setShareAddress(address) + // 绑定推荐人 + + if (!user.is_bound && ethers.utils.isAddress(address)) { + setVisibleBindRmd(true) + return + } + // 已有推荐人 + if (user.is_bound) { + setVisibleAlreadyBind(true) + } + } + + token && isModal() + !token && setVisibleBindRmd(false) + }, [token, walletAddress]) + return (
{ ) } + +
+ {/* 無效鏈接 */} + {visibleVaildLink && } + {/* 已經綁定 */} + {visibleAlreadyBind && ( + { + push('/', null, true) + setVisibleAlreadyBind(false) + push('/share') + }} + visible={visibleAlreadyBind} + setVisible={setVisibleAlreadyBind} + /> + )} + {/* 綁定推薦人 */} + {visibleBindRmd && ( + + )} + { + visibleUnLogin && ( + store.setVisibleUnLogin(false)} + /> + ) + } + {visibleDefault && ( + setVisibleDefault(false)} + onClick={bindRecommend} + /> + )} + {visibleBindSuccess && ( + setVisibleBindSuccess(false)} + address={shareAddress} + /> + )} + +
); } -export default LayoutRouter \ No newline at end of file +export default observer(LayoutRouter) \ No newline at end of file diff --git a/src/router/layout/ui.tsx b/src/router/layout/ui.tsx new file mode 100644 index 0000000..0f1204b --- /dev/null +++ b/src/router/layout/ui.tsx @@ -0,0 +1,123 @@ +import { ethers } from "ethers"; +import { useRef } from "react"; +import { Toast } from "react-vant"; +import Modal from "../../components/Modal"; +interface UIProps { + visible: boolean, + setVisible: Function, + onClick?: Function, + address?: string +} + +export const UnLogin = ({ visible, setVisible }: UIProps) => ( + +
+
訪問失敗
+
未檢測到錢包,請登錄錢包後重新點擊
+
+
+) + +export const VaildLink = ({ visible, setVisible }: UIProps) => ( + setVisible(false)} + setVisible={() => setVisible(false)} + visible={visible} + buttonText="關閉" + > +
+
綁定失敗
+
無效的分享鏈接
+
+
+) + +export const DefaultBind = ({ visible, setVisible, onClick }: UIProps) => { + + const addressRefs = useRef(null) + + const confirm = () => { + let value = addressRefs.current?.value + if (!value) return Toast.info('請輸入推薦鏈接!') + let newValue = value?.split('/#/') + if (!ethers.utils.isAddress(newValue[1])) return Toast.info('無效的分享鏈接') + onClick && onClick(newValue[1]) + } + + return ( + +
+
推薦鏈接
+ +
+
+ ) +} + +export const BindRmd = ({ visible, setVisible, address, onClick }: UIProps) => ( + setVisible(false)} + title="綁定推薦人" + buttonClick={() => onClick && onClick(address)} + buttonText="確認綁定" + hiddenCloseIcon + > +
+
推薦人地址
+
{address}
+
+
+) + +export const BindSuccess = ({ visible, setVisible, address }: UIProps) => ( + +
+
綁定成功
+
{address}
+
+
+) + + +export const AlreadyBind = ({ visible, setVisible, onClick }: UIProps) => ( + onClick && onClick()} + setVisible={() => setVisible(false)} + visible={visible} + buttonText='查看綁定人' + showCancelButton + showCancelButtonClick={() => setVisible(false)} + > +
+
綁定失敗
+
該錢包已有推薦人,不可重複綁定推薦人
+
+
+) \ No newline at end of file diff --git a/src/router/routes.tsx b/src/router/routes.tsx index eb2d627..6bc2b1a 100644 --- a/src/router/routes.tsx +++ b/src/router/routes.tsx @@ -9,12 +9,17 @@ const Detail = lazy(() => import("~/pages/detail")); const Recharge = lazy(() => import("~/pages/recharge")); const Withdraw = lazy(() => import("~/pages/withdraw")); const Record = lazy(() => import("~/pages/record")); +const Team = lazy(() => import("~/pages/team")) const routes = [ { path: "/", element: }, + { + path: '/:id', + component: + }, { path: "/product", element: , @@ -42,8 +47,11 @@ const routes = [ { path: "/record", element: , + }, + { + path: '/team', + element: } - ] as RouteObject[]; export const tabbarData = [ diff --git a/src/store/index.ts b/src/store/index.ts index fbf03c6..e650b53 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -14,6 +14,7 @@ class AppStore implements Store { coinIndex: 0, coinList: [] as CoinList[], userInfo: {} as UserInfo, + visibleUnLogin: false, }; constructor() { @@ -48,12 +49,15 @@ class AppStore implements Store { } async getUserInfo(): Promise { - const res: any = await accountInfo() + const res: any = await accountInfo(); if (res && res.code === 0) { - this.state.userInfo = res.data - window.sessionStorage.setItem('userInfo', JSON.stringify(res.data)) + this.state.userInfo = res.data; } - return res && res.data + return res && res.data; + } + + resetUserInfo() { + this.state.userInfo = {} as UserInfo; } /** @@ -73,18 +77,22 @@ class AppStore implements Store { } async getCoinList(): Promise { - const res: any = await coin_list() + const res: any = await coin_list(); if (res && res.code === 0 && res.data) { - this.state.coinList = res.data + this.state.coinList = res.data; } } resetCoinList(): void { - this.state.coinList = [] + this.state.coinList = []; } setCoinIndex(index: number): void { - this.state.coinIndex = index + this.state.coinIndex = index; + } + + setVisibleUnLogin(bool: boolean): void { + this.state.visibleUnLogin = bool } } diff --git a/src/styles/components.scss b/src/styles/components.scss index 290400c..a65b71c 100644 --- a/src/styles/components.scss +++ b/src/styles/components.scss @@ -6,8 +6,8 @@ width: 171px; height: 132px; margin-left: 5px; + object-fit:cover ; border-radius: 10px; - background: $button-background; box-shadow: 8px 8px 20px 0px rgba(0, 0, 0, 0.1); } .desc-box{ @@ -92,4 +92,21 @@ top: -15px; } } +} + +.modal-content{ + width: 340px; + border-radius: 5px; + background: $page; + padding: 20px; + color:$black; + .modal-button{ + position: relative; + width: 130px; + height: 44px; + overflow: hidden; + border-radius: 10px; + background: $button-background; + color: $white; + } } \ No newline at end of file diff --git a/src/styles/layout.scss b/src/styles/layout.scss index 524defb..15718cd 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss @@ -36,9 +36,6 @@ .pages{ height: 100%; width: 100%; - overflow: hidden; - overflow-y: scroll; - &::-webkit-scrollbar{display: none;} } .tabbar{ @@ -72,6 +69,30 @@ height: 100px; width: 100%; } + + .ui{ + .unlogin-box{ + width: 100%; + height: 32px; + border-radius: 5px; + background-color: $white; + margin-top: 10px; + color: #E45546; + font-size: 14; + } + + .default-bind-input{ + width: 100%; + border: none; + background-color: $white; + color: $black; + height: 31px; + border-radius: 5px; + text-align: center; + font-size: 12px; + margin-top: 10px; + } + } } .rv-popup::-webkit-scrollbar{ diff --git a/src/styles/personal.scss b/src/styles/personal.scss index 0ba5762..7403414 100644 --- a/src/styles/personal.scss +++ b/src/styles/personal.scss @@ -49,8 +49,93 @@ .tabs{ padding: 10px 0px; .rv-tabs__nav--capsule .rv-tab.rv-tab--active .rv-tab__text { - background-color: #EAEAEA; - color: #727272; + background-color: $primary; + color: $white; } + + } +} + +.team{ + + .mb-6px{ + margin-bottom: 8px; + } + + .fz-60{ + font-size: 60px; + } + + .card-box{ + width: 100%; + padding: 30px; + background-color: $primary; + margin-top: 10px; + border-radius: 10px; + color: $white; + } + + .button{ + width: 145px; + height: 45px; + border-radius: 10px; + background-color: $primary; + color: $white; + font-weight: bold; + } +} + +.overlay{ + + color:$white; + + .text{ + font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; + } + + .solid{ + flex:5; + width: 100%; + height: 2px; + background-color: rgba(244, 244, 244, 0.3); + } + + .share-img{ + width: 220px; + height: 220px; + object-fit: cover; + } + + .url{ + font-size: 18px; + font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; + text-align: center; + } + .button{ + margin-top: 30px; + width: 228px; + height: 52px; + border-radius: 28px; + background: $button-background; + color: $white; + position: relative; + overflow: hidden; + .text-index{ + position: relative; + z-index: 1; + display: flex; + justify-content: space-between; + align-items: center; + height: 100%; + font-family:Georgia, 'Times New Roman', Times, serif; + } + + &:active{ + opacity: 0.85; + } + } + + .swiper-height{ + // height: 460px; } } \ No newline at end of file diff --git a/src/styles/recharge.scss b/src/styles/recharge.scss index fc9d744..29515ab 100644 --- a/src/styles/recharge.scss +++ b/src/styles/recharge.scss @@ -67,5 +67,8 @@ height: 53px; background-color: $primary; border-radius: 16px; + color: $white; + font-size: 18px; + font-weight: bold; } } \ No newline at end of file diff --git a/src/styles/share.scss b/src/styles/share.scss index a180f11..cad626b 100644 --- a/src/styles/share.scss +++ b/src/styles/share.scss @@ -1,4 +1,4 @@ -.personal{ +.share{ .box{ @@ -28,12 +28,30 @@ } } + .name-box{ + height: 30px; + + input{ + border:none; + background: none; + font-weight: bold; + max-width: 200px; + text-align: center; + } + } + .box-block{ display: block; height: 71px; width: 100%; } + .text-overflow{ + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + } + .tag{ background: linear-gradient(104deg, #1DD0DF -2%, #1DD0DF -2%, #1BEDFF -2%, #14BDEB 108%); padding: 5px 15px; diff --git a/src/styles/theme.scss b/src/styles/theme.scss index 860c7ea..c88fffb 100644 --- a/src/styles/theme.scss +++ b/src/styles/theme.scss @@ -1,4 +1,4 @@ -$primary:#1BEDFF; +$primary:#0FC6D4; $background:#fff; $primary-text:#000; $sub-text:#727272; diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 5a86228..de801a7 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -9,4 +9,16 @@ interface PerformSignin { signature: string; } -export { PerformSNonce, PerformSignin } +interface HTTPWithdrawParams { + amount: string; + chainId: number; + symbol: string; +} + +interface HTTPHistoryParams { + page: number; + type: number; + type: number; +} + +export { PerformSNonce, PerformSignin, HTTPWithdrawParams, HTTPHistoryParams }; diff --git a/src/types/store.d.ts b/src/types/store.d.ts index ad54de2..9272438 100644 --- a/src/types/store.d.ts +++ b/src/types/store.d.ts @@ -15,6 +15,15 @@ interface UserInfo { show: number; total: string; url: string; + assets: Assets[]; +} + +interface Assets { + amount: string; + amount_usdt: string; + id: number; + symbol: string; + name: string; } interface CoinList { @@ -26,4 +35,42 @@ interface CoinList { withdraw_fee: string; } -export { StoreLocalStorageKey, UserInfo, CoinList }; +interface MarketNFTData { + auction_price: string; + award: string; + cast_id: number; + cast_name: string; + cast_show: number; + cast_url: string; + day: number; + end_Time: number; + function: string; + id: number; + mark: number; + name: string; + number: number; + platform: string; + price: string; + sell_id: number; + sell_name: string; + sell_show: number; + sell_url: string; + symbol: string; + time: number; + total_stock: number; + url: string; +} + +interface InviteRecordData { + address: string; + time: number; + id: number; +} + +export { + StoreLocalStorageKey, + UserInfo, + CoinList, + MarketNFTData, + InviteRecordData, +}; diff --git a/src/utils/copy.ts b/src/utils/copy.ts index d260631..3b46cdd 100644 --- a/src/utils/copy.ts +++ b/src/utils/copy.ts @@ -1,5 +1,5 @@ -export function copy(value: string, cb: Function) { +export function copy(value: string, cb?: Function) { // 动态创建 textarea 标签 const textarea: any = document.createElement('textarea') // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域 diff --git a/src/utils/index.ts b/src/utils/index.ts index fc5d673..c36e8b9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,16 +15,23 @@ const splitAddress = (address: string, index?: number) => { } }; +export const getChainId = async () => { + return toBig( + await window.ethereum.request({ + method: "eth_chainId", + }) + ).toNumber(); +}; + const switchNetWork = () => { - return new Promise((resolve) => { + return new Promise(async (resolve) => { if (!window.ethereum) { resolve(false); return; } - if ( - toBig(window.ethereum.chainId).toNumber() === 97 || - toBig(window.ethereum.chainId).toNumber() === 56 - ) { + let chainId = await getChainId(); + + if (chainId === 97 || chainId === 56) { //测试 resolve(true); return; @@ -72,4 +79,18 @@ const getTime = (value: number) => { return `${yy}-${mm}-${dd} ${xs}:${ff}`; }; -export { splitAddress, switchNetWork, toThousands, getTime }; +const toFixed2 = (val: string, double: number) => { + try { + if (val.indexOf(".") > -1) { + return val.substring( + 0, + val.indexOf(".") + (double === 0 ? double : double + 1) + ); + } + return val; + } catch (error) { + return "0"; + } +}; + +export { splitAddress, switchNetWork, toThousands, getTime, toFixed2 }; diff --git a/src/utils/sign/sign.ts b/src/utils/sign/sign.ts index 6f088ee..8d7fc3d 100644 --- a/src/utils/sign/sign.ts +++ b/src/utils/sign/sign.ts @@ -14,6 +14,7 @@ var signGenerator = (data: any) => { ptxt += keys[i] + data[keys[i]]; } ptxt = signkey + ptxt + signkey; + var signval = md5(ptxt).toLowerCase() return signval; } diff --git a/yarn.lock b/yarn.lock index e55afc5..84c4a96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2552,6 +2552,13 @@ resolved "https://registry.npmmirror.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837" integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw== +"@types/qrcode@^1.5.5": + version "1.5.5" + resolved "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac" + integrity sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.10" resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" @@ -3617,7 +3624,7 @@ camelcase-css@^2.0.1: resolved "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase@^5.3.1: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -3724,6 +3731,15 @@ clean-css@^5.2.2: dependencies: source-map "~0.6.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -4188,6 +4204,11 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decimal.js@^10.2.1: version "10.4.3" resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -4320,6 +4341,11 @@ diff-sequences@^29.6.3: resolved "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== +dijkstrajs@^1.0.1: + version "1.0.3" + resolved "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23" + integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -4514,6 +4540,11 @@ emojis-list@^3.0.0: resolved "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -5365,7 +5396,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.5: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -7662,6 +7693,11 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + postcss-attribute-case-insensitive@^5.0.2: version "5.0.2" resolved "https://registry.npmmirror.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" @@ -8330,6 +8366,16 @@ q@^1.1.2: resolved "https://registry.npmmirror.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== +qrcode@^1.5.3: + version "1.5.3" + resolved "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" + integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg== + dependencies: + dijkstrajs "^1.0.1" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" + qs@6.11.0: version "6.11.0" resolved "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -8666,6 +8712,11 @@ require-from-string@^2.0.2: resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -8971,6 +9022,11 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-function-length@^1.1.1: version "1.1.1" resolved "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" @@ -10109,6 +10165,11 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: version "1.1.13" resolved "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" @@ -10308,6 +10369,15 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -10347,6 +10417,11 @@ ws@^8.13.0: resolved "https://registry.npmmirror.com/ws/-/ws-8.15.1.tgz#271ba33a45ca0cc477940f7f200cd7fba7ee1997" integrity sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ== +ws@^8.16.0: + version "8.16.0" + resolved "https://registry.npmmirror.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -10357,6 +10432,11 @@ xmlchars@^2.2.0: resolved "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -10382,11 +10462,36 @@ yaml@^2.3.4: resolved "https://registry.npmmirror.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^16.2.0: version "16.2.0" resolved "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"