diff --git a/infra/rooch-portal-v2/src/app/invitation/[address]/page.tsx b/infra/rooch-portal-v2/src/app/invitation/[address]/page.tsx index f9d227c635..816468ce0d 100644 --- a/infra/rooch-portal-v2/src/app/invitation/[address]/page.tsx +++ b/infra/rooch-portal-v2/src/app/invitation/[address]/page.tsx @@ -1,6 +1,6 @@ -import { InvitationsView } from 'src/sections/invitations/index'; +import { InvitationsView } from 'src/sections/invitations/view'; -export const metadata = { title: `Invitation` }; +export const metadata = { title: `Search Invitation` }; export default function Page({ params }: { params: { address: string } }) { return ; diff --git a/infra/rooch-portal-v2/src/app/invitation/page..tsx b/infra/rooch-portal-v2/src/app/invitation/page..tsx deleted file mode 100644 index a95260cfd4..0000000000 --- a/infra/rooch-portal-v2/src/app/invitation/page..tsx +++ /dev/null @@ -1,8 +0,0 @@ - -import { InvitationsView } from 'src/sections/invitations/index'; - -export const metadata = { title: `Invitation` }; - -export default function Page() { - return ; -} diff --git a/infra/rooch-portal-v2/src/app/invitation/page.tsx b/infra/rooch-portal-v2/src/app/invitation/page.tsx new file mode 100644 index 0000000000..c956278188 --- /dev/null +++ b/infra/rooch-portal-v2/src/app/invitation/page.tsx @@ -0,0 +1,11 @@ + +import WalletGuard from 'src/components/guard/WalletGuard'; +import InvitationOverviewView from 'src/sections/invitations/overview'; + +export const metadata = { title: `Invitation` }; + +export default function Page() { + return ( + + ); +} diff --git a/infra/rooch-portal-v2/src/layouts/config-nav-dashboard.tsx b/infra/rooch-portal-v2/src/layouts/config-nav-dashboard.tsx index 72a15de5da..72a01ce931 100644 --- a/infra/rooch-portal-v2/src/layouts/config-nav-dashboard.tsx +++ b/infra/rooch-portal-v2/src/layouts/config-nav-dashboard.tsx @@ -36,13 +36,11 @@ export const navData = [ title: 'Faucet', path: paths.dashboard.faucet, icon: , - // noAddressRequired: true, }, { title: 'Invitation', path: paths.dashboard.invitation, icon: , - // noAddressRequired: true, }, { title: 'Settings', diff --git a/infra/rooch-portal-v2/src/sections/faucet/inviter.tsx b/infra/rooch-portal-v2/src/sections/faucet/inviter.tsx index 394b7bfb31..6a01821faf 100644 --- a/infra/rooch-portal-v2/src/sections/faucet/inviter.tsx +++ b/infra/rooch-portal-v2/src/sections/faucet/inviter.tsx @@ -1,12 +1,14 @@ 'use client'; +import type { Bytes} from '@roochnetwork/rooch-sdk'; + import { useState, useEffect } from 'react'; -import { Args, stringToBytes, toHEX, Bytes } from '@roochnetwork/rooch-sdk'; +import { Args, toHEX, stringToBytes } from '@roochnetwork/rooch-sdk'; import { - useCurrentAddress, useRoochClient, - useRoochClientQuery, useCurrentWallet, + useCurrentAddress, + useRoochClientQuery, } from '@roochnetwork/rooch-sdk-kit'; import { LoadingButton } from '@mui/lab'; @@ -17,12 +19,12 @@ import { useRouter } from 'src/routes/hooks'; import { useNetworkVariable } from 'src/hooks/use-networks'; import { formatCoin } from 'src/utils/format-number'; +import { INVITER_ADDRESS_KEY } from 'src/utils/inviter'; import { DashboardContent } from 'src/layouts/dashboard'; import { toast } from 'src/components/snackbar'; -import { INVITER_ADDRESS_KEY } from 'src/utils/inviter'; import { paths } from '../../routes/paths'; const FAUCET_NOT_OPEN = 'Faucet Not Open'; @@ -167,7 +169,6 @@ export function InviterFaucetView({ inviterAddress }: { inviterAddress: string } if (!response.ok) { const data = await response.json(); - console.log(data); if (response.status === 500 && data.error.includes('UTXO value is zero')) { const msg = 'Claim failed, Not found UTXO'; setErrorMsg(msg); diff --git a/infra/rooch-portal-v2/src/sections/faucet/view.tsx b/infra/rooch-portal-v2/src/sections/faucet/view.tsx index 81e9627b0b..46c058136a 100644 --- a/infra/rooch-portal-v2/src/sections/faucet/view.tsx +++ b/infra/rooch-portal-v2/src/sections/faucet/view.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { Args, isValidBitcoinAddress } from '@roochnetwork/rooch-sdk'; -import { useRoochClient, useRoochClientQuery } from '@roochnetwork/rooch-sdk-kit'; +import { useCurrentNetwork, useRoochClient, useRoochClientQuery } from '@roochnetwork/rooch-sdk-kit'; import { LoadingButton } from '@mui/lab'; import { Box, Card, Chip, Stack, CardHeader, CardContent } from '@mui/material'; @@ -19,8 +19,9 @@ import { DashboardContent } from 'src/layouts/dashboard'; import { toast } from 'src/components/snackbar'; +import { CopyToClipboard } from 'react-copy-to-clipboard' import { paths } from '../../routes/paths' -import { INVITER_ADDRESS_KEY } from "../../utils/inviter"; +import { INVITER_ADDRESS_KEY } from "../../utils/inviter" const FAUCET_NOT_OPEN= 'Faucet Not Open' const INVALID_UTXO = 'Invalid UTXO' @@ -47,6 +48,7 @@ export function FaucetView({ address }: { address: string }) { const faucetObject = useNetworkVariable('faucetObject'); const [claimGas, setClaimGas] = useState(0); const router = useRouter(); + const network = useCurrentNetwork() useAddressChanged({ address, path: 'faucet' }); @@ -170,7 +172,16 @@ export function FaucetView({ address }: { address: string }) { return ( - + + + + { + toast.success('Copy to you clipboard') + }} text={`https://${network === 'mainnet' ? '':'test-'}portal.rooch.network/inviter/${viewAddress}`}> + + + + diff --git a/infra/rooch-portal-v2/src/sections/invitations/overview.tsx b/infra/rooch-portal-v2/src/sections/invitations/overview.tsx new file mode 100644 index 0000000000..66ba14c47c --- /dev/null +++ b/infra/rooch-portal-v2/src/sections/invitations/overview.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { useEffect } from 'react'; +import { useCurrentAddress } from '@roochnetwork/rooch-sdk-kit'; + +import { useRouter } from 'src/routes/hooks'; + +export default function InvitationOverviewView() { + const address = useCurrentAddress(); + const router = useRouter(); + useEffect(() => { + if (address) { + router.push(`/invitation/${address.toStr()}`); + } + }, [address, router]); + if (!address) { + return null; + } + return null; +} diff --git a/infra/rooch-portal-v2/src/sections/invitations/index.tsx b/infra/rooch-portal-v2/src/sections/invitations/view.tsx similarity index 100% rename from infra/rooch-portal-v2/src/sections/invitations/index.tsx rename to infra/rooch-portal-v2/src/sections/invitations/view.tsx diff --git a/infra/rooch-portal-v2/src/sections/settings/view.tsx b/infra/rooch-portal-v2/src/sections/settings/view.tsx index 00e5414da0..c8c868c1a2 100644 --- a/infra/rooch-portal-v2/src/sections/settings/view.tsx +++ b/infra/rooch-portal-v2/src/sections/settings/view.tsx @@ -33,14 +33,15 @@ export function SettingsView() { const session = useCurrentSession() const client = useRoochClient() + const wallet = useCurrentWallet() const network = useCurrentNetwork() const faucetUrl = useNetworkVariable('faucetUrl') const twitterOracleAddress = useNetworkVariable('twitterOracleAddress') const [inviterCA, inviterModule, inviterConf] = useNetworkVariable('inviterCA'); - const wallet = useCurrentWallet() const [tweetStatus, setTweetStatus] = useState('') const [twitterId, setTwitterId] = useState() const [verifying, setVerifying] = useState(false) + const [fetchTwitterIdStatus, setFetchTwitterIdStatus] = useState(true) const { data: sessionKeys, @@ -78,9 +79,56 @@ export function SettingsView() { }, [address, client, twitterOracleAddress]) useEffect(() => { - fetchTwitterId() + fetchTwitterId().finally(() => setFetchTwitterIdStatus(false)) }, [fetchTwitterId]) + const loopFetchTwitterId = async (count = 0) => { + const id = await fetchTwitterId() + + if (id || count === 3) { + return id + } + + return loopFetchTwitterId(count +1) + } + + const checkTwitterObj = async (id: string) => { + const result = await client.queryObjectStates({ + filter: { + object_id: id + } + }) + + if (result.data.length === 0) { + await sleep(10000) + return checkTwitterObj(id) + } + + // TODO: twitter post btc address !== current wallet address. + // if (result.data[0].owner_bitcoin_address !== address?.toStr()) { + // throw (new Error('The twitter post btc address does not match the wallet address')) + // } + + return '' + } + const fetchTwitterPost = async (pureTweetId: string) => { + const res = await axios.post( + `${faucetUrl}/fetch-tweet`, + { + tweet_id: pureTweetId, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + + if (res.data.ok) { + await checkTwitterObj(res.data.ok) + } + } + const disconnectTwitter = async () => { if (!session) { return @@ -157,26 +205,10 @@ export function SettingsView() { return } setVerifying(true) - const pureTweetId = match[1] try { const pureTweetId = match[1] - const res = await axios.post( - `${faucetUrl}/fetch-tweet`, - { - tweet_id: pureTweetId, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ) - - if (!res.data.ok) { - toast.error('fetch twitter failed') - return - } + await fetchTwitterPost(pureTweetId) // step 2, check inviter const inviterAddr = window.localStorage.getItem(INVITER_ADDRESS_KEY) @@ -196,12 +228,14 @@ export function SettingsView() { } else { await bindTwitter(pureTweetId) } + } else { + await bindTwitter(pureTweetId) + } - await sleep(3000) - const checkRes = await fetchTwitterId() - if (checkRes) { - toast.success('Binding success') - } + await sleep(3000) + const checkRes = await loopFetchTwitterId() + if (checkRes) { + toast.success('Binding success') } } catch(error) { if ('response' in error) { @@ -218,7 +252,7 @@ export function SettingsView() { } } - const networkText = network === 'mainnet' ? 'Pre-mainnet' : 'Testnet' + const networkText = network === 'mainnet' ? 'PreMainnet' : 'Testnet' const XText = `BTC:${address?.toStr()} Rooch ${networkText} is live! Bind your Twitter to earn RGas, and visit https://${network === 'mainnet' ? '':'test-'}grow.rooch.network to earn rewards with your BTC. @@ -256,7 +290,7 @@ https://${network === 'mainnet' ? '':'test-'}portal.rooch.network/inviter/${addr subheader="Bind a Twitter account to a Bitcoin address via publishing a tweet" /> - {twitterId ? ( + {fetchTwitterIdStatus ? <>: twitterId ? (