diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 59896d45..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "" -labels: "Type: bug" -assignees: "" ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - -- OS: [e.g. iOS] -- Browser [e.g. chrome, safari] -- Version [e.g. 22] - -**Smartphone (please complete the following information):** - -- Device: [e.g. iPhone6] -- OS: [e.g. iOS8.1] -- Browser [e.g. stock browser, safari] -- Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/contributor-issue.md b/.github/ISSUE_TEMPLATE/contributor-issue.md new file mode 100644 index 00000000..0465d4f6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/contributor-issue.md @@ -0,0 +1,47 @@ +--- +name: Contributor Issue +about: Create an issue for it to be taken by a contributor +title: Name your issue +labels: Good first issue, OD Hack, open for contribution +assignees: '' + +--- + +## Description 📹 + +**[Insert clear description here]** + +## Proposed Actions 🛠️ + +Here’s a checklist of actions to follow for resolving this issue: + +1. **Fork and Create Branch**: + Fork the repository and create a new branch using the issue number: + ```bash + git checkout -b fix-[issue-number] + ``` + +2. **Implement Changes**: + [Insert Code snippet if needed with a mardown todo list] + +3. **Run Tests and Commit Changes**: + Make sure your changes don't break existing functionality and commit with a clear message: + ```bash + git commit -m "Fix: [Short description of the fix]" + ``` + +## Required 📋 + +To keep our workflow smooth, please make sure you follow these guidelines: + +- **Assignment**: Don't create a pull request if you weren’t assigned to this issue. +- **Timeframe**: Complete the task within **3 business days**. +- **Closing the Issue**: In your PR description, close the issue by writing `Close #[issue_id]`. +- **Review Process**: + - Once you've submitted your PR, change the label to **"ready for review"**. + - If changes are requested, address them and then update the label back to **"ready for review"** once done. +- **Testing**: Test your PR locally before pushing, and verify that tests and build are working after pushing. + +Thank you for your contribution 🙏 + +⚠️ WARNING: Failure to follow the requirements above may result in being added to the OnlyDust blacklist, affecting your ability to receive future rewards. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 96e97b42..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature request -about: Describe the feature you want and propose some steps to achieve it -title: "" -labels: "" -assignees: "" ---- - -## Description -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -## PROPOSED TODO - -- [ ] Your task number 1 -- [ ] Your task number 2 -- [ ] Your task number 3 -... - - diff --git a/README.md b/README.md index 31d253be..56879184 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Leading quests platform on Starknet to onboard your next million users. We provide a platform for users to discover new apps and for companies to pitch your products and new features to users and get user feedback. -[![Pull Requests welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg?style=flat-square)](https://github.com/starknet-id/starknet.quest/issues?q=is:issue+is:open+label:%22open+for+contribution%22) +[![Pull Requests welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg?style=flat-square)](https://github.com/lfglabs-dev/starknet.quest/pulls) ## About @@ -49,6 +49,17 @@ npm i npm run dev ``` +If you encounter installation issues, try these steps: + +* Clear npm cache: `npm cache clean --force` +* Delete node_modules: `rm -rf node_modules` +* Delete package-lock.json: `rm package-lock.json` +* Retry installation: `npm i` + +If issues persist, you can try `npm i --legacy-peer-deps` or `npm i --force` as a last resort. + +Note that using these flags may lead to dependency conflicts. + You should see something like this: ```sh @@ -100,4 +111,4 @@ Thanks go to these wonderful people: This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. -Contributions of any kind welcome! \ No newline at end of file +Contributions of any kind welcome! diff --git a/app/[addressOrDomain]/page.tsx b/app/[addressOrDomain]/page.tsx index 534a8205..308b54b9 100644 --- a/app/[addressOrDomain]/page.tsx +++ b/app/[addressOrDomain]/page.tsx @@ -12,13 +12,19 @@ import { useAccount } from "@starknet-react/core"; import Blur from "@components/shapes/blur"; import { utils } from "starknetid.js"; import { StarknetIdJsContext } from "@context/StarknetIdJsProvider"; -import { hexToDecimal } from "@utils/feltService"; +import { hexToDecimal, tokenToDecimal } from "@utils/feltService"; import { isHexString, minifyAddress } from "@utils/stringService"; import ProfileCardSkeleton from "@components/skeletons/profileCardSkeleton"; import { getDataFromId } from "@services/starknetIdService"; import { usePathname, useRouter } from "next/navigation"; import ErrorScreen from "@components/UI/screens/errorScreen"; -import { CompletedQuests } from "../../types/backTypes"; +import { + ArgentDappMap, + ArgentTokenMap, + ArgentUserDapp, + ArgentUserToken, + CompletedQuests, +} from "../../types/backTypes"; import QuestSkeleton from "@components/skeletons/questsSkeleton"; import QuestCardCustomised from "@components/dashboard/CustomisedQuestCard"; import QuestStyles from "@styles/Home.module.css"; @@ -31,6 +37,17 @@ import { TEXT_TYPE } from "@constants/typography"; import { a11yProps } from "@components/UI/tabs/a11y"; import { CustomTabPanel } from "@components/UI/tabs/customTab"; import SuggestedQuests from "@components/dashboard/SuggestedQuests"; +import PortfolioSummary from "@components/dashboard/PortfolioSummary"; +import { useNotification } from "@context/NotificationProvider"; +import { + calculateTokenPrice, + fetchDapps, + fetchTokens, + fetchUserDapps, + fetchUserTokens, +} from "@services/argentPortfolioService"; +import PortfolioSummarySkeleton from "@components/skeletons/portfolioSummarySkeleton"; +import { useDisplayName } from "@hooks/displayName.tsx"; type AddressOrDomainProps = { params: { @@ -38,10 +55,23 @@ type AddressOrDomainProps = { }; }; +type ChartItemMap = { + [dappId: string]: ChartItem; +}; + +type DebtStatus = { + hasDebt: boolean; + tokens: { + dappId: string; + tokenAddress: string; + tokenBalance: number; + }[]; +}; + export default function Page({ params }: AddressOrDomainProps) { const router = useRouter(); const addressOrDomain = params.addressOrDomain; - const { address } = useAccount(); + const { showNotification } = useNotification(); const { starknetIdNavigator } = useContext(StarknetIdJsContext); const [initProfile, setInitProfile] = useState(false); const { getBoostClaimStatus } = useBoost(); @@ -62,6 +92,22 @@ export default function Page({ params }: AddressOrDomainProps) { const [questsLoading, setQuestsLoading] = useState(true); const [tabIndex, setTabIndex] = React.useState(0); const [claimableQuests, setClaimableQuests] = useState([]); + const [portfolioAssets, setPortfolioAssets] = useState([]); + const [portfolioProtocols, setPortfolioProtocols] = useState([]); + const [loadingProtocols, setLoadingProtocols] = useState(true); + const { address } = useAccount(); + + useEffect(() => { + if (!address) return; + + // Redirect to the new address profile + const updateUrl = (address: string) => { + const newUrl = `/${address}`; + router.replace(newUrl); + }; + + updateUrl(address); + }, [address, router]); const handleChangeTab = useCallback( (event: React.SyntheticEvent, newValue: number) => { @@ -168,10 +214,404 @@ export default function Page({ params }: AddressOrDomainProps) { setQuestsLoading(false); }, []); + const calculateAssetPercentages = async ( + userTokens: ArgentUserToken[], + tokens: ArgentTokenMap, + dapps: ArgentDappMap, + userDapps: ArgentUserDapp[] + ) => { + let totalValue = 0; + const assetValues: { [symbol: string]: number } = {}; + + // Process user tokens in parallel + const userTokenPromises = userTokens.map(async (token) => { + const tokenInfo = tokens[token.tokenAddress]; + if (!tokenInfo || token.tokenBalance === "0") return null; + + // Skip protocol tokens (like LPT pair tokens, staking, etc.) + if (tokenInfo.dappId) { + return null; + } + + try { + const value = await calculateTokenPrice( + token.tokenAddress, + tokenToDecimal(token.tokenBalance, tokenInfo.decimals), + "USD" + ); + return { + value, + symbol: tokenInfo.symbol || "Unknown", + isProtocolToken: !!tokenInfo.dappId, + }; + } catch (err) { + console.log( + `Error calculating price for token ${token.tokenAddress}:`, + err + ); + return null; + } + }); + + // Flatten userDapps into an array of token balances + const dappBalances = userDapps.flatMap( + (dapp) => + dapp.products[0]?.positions.flatMap((position) => + Object.entries(position.totalBalances).map( + ([tokenAddress, balance]) => ({ + tokenAddress, + balance, + dappId: dapp.dappId, + }) + ) + ) ?? [] + ); + + // Process all balances in parallel + const balancePromises = dappBalances.map( + async ({ tokenAddress, balance, dappId }) => { + const tokenInfo = tokens[tokenAddress]; + if (!tokenInfo || balance === "0") return null; + + try { + const value = await calculateTokenPrice( + tokenAddress, + tokenToDecimal(balance, tokenInfo.decimals), + "USD" + ); + + return { + value, + symbol: tokenInfo.symbol || "Unknown", + isProtocolToken: !!tokenInfo.dappId, + }; + } catch (err) { + console.log( + `Error calculating price for token ${tokenAddress}:`, + err + ); + return null; + } + } + ); + + // Process results + const results = ( + await Promise.all([...balancePromises, ...userTokenPromises]) + ).filter(Boolean); + + results.forEach((result) => { + if (!result) return; + const { value, symbol, isProtocolToken } = result; + + if (value < 0) return; // Skip negative balances + + totalValue += value; + + if (!isProtocolToken) { + assetValues[symbol] = (assetValues[symbol] || 0) + value; + } + }); + // Convert to percentages and format + const sortedAssets = Object.entries(assetValues) + .sort(([, a], [, b]) => b - a) + .map(([symbol, value]) => ({ + itemLabel: symbol, + itemValue: ((value / totalValue) * 100).toFixed(2), + itemValueSymbol: "%", + color: "", // Colors will be assigned later + })); + + // Handle "Others" category if needed + if (sortedAssets.length > 4) { + const others = sortedAssets + .slice(4) + .reduce((sum, asset) => sum + parseFloat(asset.itemValue), 0); + sortedAssets.splice(4); + sortedAssets.push({ + itemLabel: "Others", + itemValue: others.toFixed(2), + itemValueSymbol: "%", + color: "", + }); + } + + // Assign colors + const colors = ["#1E2097", "#637DEB", "#2775CA", "#5CE3FE", "#F4FAFF"]; + sortedAssets.forEach((asset, index) => { + asset.color = colors[index % colors.length]; // Use modulo to recycle colors if needed + }); + return sortedAssets; + }; + + const fetchPortfolioAssets = useCallback( + async (data: { + dapps: ArgentDappMap; + tokens: ArgentTokenMap; + userTokens: ArgentUserToken[]; + userDapps: ArgentUserDapp[]; + }) => { + const { dapps, tokens, userTokens, userDapps } = data; + try { + if (!tokens || !userTokens || !dapps || !userDapps) { + console.warn("Missing required data for portfolio calculation"); + return; + } + const assets = await calculateAssetPercentages( + userTokens, + tokens, + dapps, + userDapps + ); + setPortfolioAssets(assets); + } catch (error) { + showNotification("Error while fetching portfolio assets", "error"); + console.log("Error while fetching portfolio assets", error); + } + }, + [] + ); + + const userHasDebt = (userDapps: ArgentUserDapp[]) => { + let debt: DebtStatus = { hasDebt: false, tokens: [] }; + + for (const dapp of userDapps) { + if (!dapp.products[0]) { + continue; + } + for (const position of dapp.products[0].positions) { + for (const tokenAddress of Object.keys(position.totalBalances)) { + const tokenBalance = Number(position.totalBalances[tokenAddress]); + if (tokenBalance < 0) { + debt.hasDebt = true; + debt.tokens.push({ + dappId: dapp.dappId, + tokenAddress, + tokenBalance, + }); + } + } + } + } + return debt; + }; + + const handleDebt = async ( + protocolsMap: ChartItemMap, + userDapps: ArgentUserDapp[], + tokens: ArgentTokenMap + ) => { + const debtStatus = userHasDebt(userDapps); + if (!debtStatus || !debtStatus.hasDebt) { + return; + } + + for await (const debt of debtStatus.tokens) { + let value = Number(protocolsMap[debt.dappId].itemValue); + value += await calculateTokenPrice( + debt.tokenAddress, + tokenToDecimal( + debt.tokenBalance.toString(), + tokens[debt.tokenAddress].decimals + ), + "USD" + ); + + protocolsMap[debt.dappId].itemValue = value.toFixed(2); + } + }; + + const getProtocolsFromTokens = async ( + protocolsMap: ChartItemMap, + userTokens: ArgentUserToken[], + tokens: ArgentTokenMap, + dapps: ArgentDappMap + ) => { + for await (const token of userTokens) { + const tokenInfo = tokens[token.tokenAddress]; + if (tokenInfo.dappId && token.tokenBalance != "0") { + let itemValue = 0; + const currentTokenBalance = await calculateTokenPrice( + token.tokenAddress, + tokenToDecimal(token.tokenBalance, tokenInfo.decimals), + "USD" + ); + + if (protocolsMap[tokenInfo.dappId]?.itemValue) { + itemValue = + Number(protocolsMap[tokenInfo.dappId].itemValue) + + currentTokenBalance; + } else { + itemValue = currentTokenBalance; + } + + protocolsMap[tokenInfo.dappId] = { + color: "", + itemLabel: dapps[tokenInfo.dappId].name, + itemValueSymbol: "$", + itemValue: itemValue.toFixed(2), + }; + } + } + }; + + const getProtocolsFromDapps = async ( + protocolsMap: ChartItemMap, + userDapps: ArgentUserDapp[], + tokens: ArgentTokenMap, + dapps: ArgentDappMap + ) => { + for await (const userDapp of userDapps) { + if (protocolsMap[userDapp.dappId]) { + continue; + } // Ignore entry if already present in the map + + let protocolBalance = 0; + if (!userDapp.products[0]) { + return; + } + for await (const position of userDapp.products[0].positions) { + for await (const tokenAddress of Object.keys(position.totalBalances)) { + protocolBalance += await calculateTokenPrice( + tokenAddress, + tokenToDecimal( + position.totalBalances[tokenAddress], + tokens[tokenAddress].decimals + ), + "USD" + ); + } + } + + protocolsMap[userDapp.dappId] = { + color: "", + itemLabel: dapps[userDapp.dappId].name, + itemValueSymbol: "$", + itemValue: protocolBalance.toFixed(2), + }; + } + }; + + const sortProtocols = (protocolsMap: ChartItemMap) => { + return Object.values(protocolsMap).sort( + (a, b) => parseFloat(b.itemValue) - parseFloat(a.itemValue) + ); + }; + + const handleExtraProtocols = (sortedProtocols: ChartItem[]) => { + let otherProtocols = + sortedProtocols.length > 5 ? sortedProtocols.splice(4) : []; + if (otherProtocols.length === 0) { + return; + } + sortedProtocols.push({ + itemLabel: "Others", + itemValue: otherProtocols + .reduce( + (valueSum, protocol) => valueSum + Number(protocol.itemValue), + 0 + ) + .toFixed(2), + itemValueSymbol: "$", + color: "", + }); + }; + + const assignProtocolColors = (sortedProtocols: ChartItem[]) => { + const portfolioProtocolColors = [ + "#278015", + "#23F51F", + "#DEFE5C", + "#9EFABB", + "#F4FAFF", + ]; + sortedProtocols.forEach((protocol, index) => { + protocol.color = portfolioProtocolColors[index]; + }); + }; + + const fetchPortfolioProtocols = useCallback( + async (data: { + dapps: ArgentDappMap; + tokens: ArgentTokenMap; + userTokens: ArgentUserToken[]; + userDapps: ArgentUserDapp[]; + }) => { + const { dapps, tokens, userTokens, userDapps } = data; + + if (!dapps || !tokens || (!userTokens && !userDapps)) return; + let protocolsMap: ChartItemMap = {}; + + try { + await getProtocolsFromTokens(protocolsMap, userTokens, tokens, dapps); + await handleDebt(protocolsMap, userDapps, tokens); // Tokens show debt as balance 0, so need to handle it manually + await getProtocolsFromDapps(protocolsMap, userDapps, tokens, dapps); + + let sortedProtocols = sortProtocols(protocolsMap); + handleExtraProtocols(sortedProtocols); + assignProtocolColors(sortedProtocols); + + setPortfolioProtocols(sortedProtocols); + } catch (error) { + showNotification( + "Error while calculating address portfolio stats", + "error" + ); + console.log("Error while calculating address portfolio stats", error); + } + }, + [address] + ); + + const fetchPortfolioData = useCallback( + async (addr: string, abortController: AbortController) => { + setLoadingProtocols(true); + try { + // Argent API requires lowercase address + const normalizedAddr = addr.toLowerCase(); + const [dappsData, tokensData, userTokensData, userDappsData] = + await Promise.all([ + fetchDapps({ signal: abortController.signal }), + fetchTokens({ signal: abortController.signal }), + fetchUserTokens(normalizedAddr, { signal: abortController.signal }), + fetchUserDapps(normalizedAddr, { signal: abortController.signal }), + ]); + + const data = { + dapps: dappsData, + tokens: tokensData, + userTokens: userTokensData, + userDapps: userDappsData, + }; + + await Promise.all([ + fetchPortfolioProtocols(data), + fetchPortfolioAssets(data), + ]); + } catch (error) { + console.log("Error while fetching address portfolio", error); + if (error instanceof Error && error.name === "AbortError") { + // Do not show notification for AbortError + return; + } + + showNotification("Error while fetching address portfolio", "error"); + } finally { + setLoadingProtocols(false); + } + }, + [fetchPortfolioProtocols, fetchPortfolioAssets] + ); + useEffect(() => { + const abortController = new AbortController(); + if (!identity) return; fetchQuestData(identity.owner); fetchPageData(identity.owner); + fetchPortfolioData(identity.owner, abortController); + + return () => abortController.abort(); }, [identity]); useEffect(() => setNotFound(false), [dynamicRoute]); @@ -296,8 +736,8 @@ export default function Page({ params }: AddressOrDomainProps) { if (notFound) { return ( router.push("/")} /> ); @@ -315,7 +755,6 @@ export default function Page({ params }: AddressOrDomainProps) { {initProfile && identity ? ( + {/* Portfolio charts */} +
+ {loadingProtocols ? ( // Change for corresponding state + + ) : ( + sum + Number(item.itemValue), + 0 + )} + isProtocol={false} + /> + )} + {loadingProtocols ? ( + + ) : ( + sum + Number(item.itemValue), + 0 + )} + isProtocol={true} + /> + )} +
+ {/* Completed Quests */}
@@ -332,11 +801,11 @@ export default function Page({ params }: AddressOrDomainProps) { style={{ borderBottom: "0.5px solid rgba(224, 224, 224, 0.3)", }} - className="pb-6" + className='pb-6' value={tabIndex} onChange={handleChangeTab} - aria-label="quests and collectons tabs" - indicatorColor="secondary" + aria-label='quests and collectons tabs' + indicatorColor='secondary' >
- +
{questsLoading ? ( ) : completedQuests?.length === 0 ? ( - isOwner - ? - : User has not completed any quests at the moment + isOwner ? ( + + ) : ( + + User has not completed any quests at the moment + + ) ) : (
{completedQuests?.length > 0 && completedQuests?.map((quest) => ( - + ))}
@@ -391,11 +873,14 @@ export default function Page({ params }: AddressOrDomainProps) {
- + {questsLoading ? ( ) : ( -
+
{claimableQuests && claimableQuests.map((quest) => ( ({ + best_users: [], + total_users: 0, + }); + const isTop50RankedView = useMemo( () => !currentSearchedAddress && @@ -87,18 +94,21 @@ export default function Page() { // set user address on wallet connect and disconnect useEffect(() => { - setTimeout(() => { - setApiCallDelay(true); - }, 1000); - if (address === "") return; + const timeoutId = setTimeout(() => setApiCallDelay(true), 1000); if (address) setUserAddress(address); if (status === "disconnected") setUserAddress(""); + return () => clearTimeout(timeoutId); // Cleanup }, [address, status]); useEffect(() => { if (!apiCallDelay) return; - fetchPageData(); - }, [apiCallDelay]); + const fetchTimeout = setTimeout(() => { + fetchPageData(); + }, 500); + + return () => clearTimeout(fetchTimeout); +}, [apiCallDelay]); + const fetchRankingResults = useCallback( async (requestBody: LeaderboardRankingParams) => { @@ -113,10 +123,9 @@ export default function Page() { const addRankingResults = useCallback( async (requestBody: LeaderboardRankingParams) => { const response = await fetchLeaderboardRankings(requestBody); - if (response) - setRanking((prev) => { - return { ...prev, ranking: [...prev.ranking, ...response.ranking] }; - }); + if (response) { + setRanking((prev) => ({ ...prev, ranking: [...prev.ranking, ...response.ranking] })); + } }, [] ); @@ -131,10 +140,7 @@ export default function Page() { const fetchPageData = useCallback(async () => { const requestBody = { - addr: - status === "connected" - ? hexToDecimal(address && address?.length > 0 ? address : userAddress) - : "", + addr: status === "connected" ? (address || userAddress) : "", page_size: 10, shift: 0, duration: timeFrameMap(duration), @@ -154,11 +160,7 @@ export default function Page() { status, ]); - const [leaderboardToppers, setLeaderboardToppers] = - useState({ - best_users: [], - total_users: 0, - }); + const contract = useMemo(() => { return new Contract( @@ -206,10 +208,7 @@ export default function Page() { useEffect(() => { const checkIfValidAddress = async (address: string) => { try { - let domain = address; - if (isStarkDomain(address)) { - domain = getDomainWithoutStark(address); - } + const domain = isStarkDomain(address) ? getDomainWithoutStark(address) : address; const res: { message: boolean } = await verifyDomain(domain); if (res.message) { setSearchResults([domain.concat(".stark")]); @@ -260,12 +259,7 @@ export default function Page() { } if (!checkIfLastPage && viewMore) { const requestBody = { - addr: - currentSearchedAddress.length > 0 - ? currentSearchedAddress - : userAddress - ? hexToDecimal(userAddress) - : "", + addr: currentSearchedAddress || (userAddress ? (userAddress) : ""), page_size: rowsPerPage, shift: currentPage, duration: timeFrameMap(duration), @@ -294,14 +288,7 @@ export default function Page() { */ useEffect(() => { const requestBody = { - addr: - currentSearchedAddress.length > 0 - ? currentSearchedAddress - : userAddress - ? hexToDecimal(userAddress) - : address - ? address - : "", + addr: currentSearchedAddress || (userAddress ? (userAddress) : address || ""), page_size: rowsPerPage, shift: 0, duration: timeFrameMap(duration), @@ -326,14 +313,7 @@ export default function Page() { useEffect(() => { if (inititalFetchTop50 && address && duration !== TOP_50_TAB_STRING) { const requestBody = { - addr: - currentSearchedAddress.length > 0 - ? currentSearchedAddress - : userAddress - ? hexToDecimal(userAddress) - : address - ? address - : "", + addr: currentSearchedAddress || (userAddress ? (userAddress) : address || ""), page_size: rowsPerPage, shift: 0, duration: timeFrameMap(duration), @@ -453,7 +433,7 @@ export default function Page() { 0 - ? currentSearchedAddress + ? decimalToHex(currentSearchedAddress) : userAddress } /> @@ -544,7 +524,7 @@ export default function Page() { selectedAddress={ currentSearchedAddress.length > 0 ? currentSearchedAddress - : hexToDecimal(userAddress) + : (userAddress) } searchedAddress={currentSearchedAddress} leaderboardToppers={leaderboardToppers} diff --git a/app/not-connected/page.tsx b/app/not-connected/page.tsx index 952eea78..7886c4ce 100644 --- a/app/not-connected/page.tsx +++ b/app/not-connected/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useAccount, useConnect } from "@starknet-react/core"; +import { useAccount, useConnect, Connector } from "@starknet-react/core"; import React, { useEffect } from "react"; import { useRouter } from "next/navigation"; import ErrorScreen from "@components/UI/screens/errorScreen"; @@ -12,7 +12,7 @@ export default function Page() { const { connectAsync } = useConnect(); const { push } = useRouter(); const { starknetkitConnectModal } = useStarknetkitConnectModal({ - connectors: availableConnectors, + connectors: availableConnectors as any, modalTheme: "dark", }); @@ -25,14 +25,14 @@ export default function Page() { if (!connector) { return; } - await connectAsync({ connector }); + await connectAsync({ connector: connector as Connector }); // Type casted localStorage.setItem("SQ-connectedWallet", connector.id); }; return ( <> diff --git a/app/provider.tsx b/app/provider.tsx index a85944a5..0ec6b2fb 100644 --- a/app/provider.tsx +++ b/app/provider.tsx @@ -1,30 +1,30 @@ -"use client"; +'use client'; -import React from "react"; -import { InjectedConnector } from "starknetkit/injected"; -import { WebWalletConnector } from "starknetkit/webwallet"; -import { ArgentMobileConnector } from "starknetkit/argentMobile"; -import { Chain, mainnet, sepolia } from "@starknet-react/chains"; +import React from 'react'; +import { InjectedConnector } from 'starknetkit/injected'; +import { WebWalletConnector } from 'starknetkit/webwallet'; +import { ArgentMobileConnector } from 'starknetkit/argentMobile'; +import { Chain, mainnet, sepolia } from '@starknet-react/chains'; import { Connector, StarknetConfig, jsonRpcProvider, -} from "@starknet-react/core"; -import { StarknetIdJsProvider } from "@context/StarknetIdJsProvider"; -import { ThemeProvider, createTheme } from "@mui/material"; -import { QuestsContextProvider } from "@context/QuestsProvider"; -import { getCurrentNetwork } from "@utils/network"; -import { constants } from "starknet"; -import { PostHogProvider } from "posthog-js/react"; -import posthog from "posthog-js"; -import { NotificationProvider } from "@context/NotificationProvider"; -import { LocalizationProvider } from "@mui/x-date-pickers"; -import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +} from '@starknet-react/core'; +import { StarknetIdJsProvider } from '@context/StarknetIdJsProvider'; +import { ThemeProvider, createTheme } from '@mui/material'; +import { QuestsContextProvider } from '@context/QuestsProvider'; +import { getCurrentNetwork } from '@utils/network'; +import { constants } from 'starknet'; +import { PostHogProvider } from 'posthog-js/react'; +import posthog from 'posthog-js'; +import { NotificationProvider } from '@context/NotificationProvider'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; // Traffic measures -if (typeof window !== "undefined") { +if (typeof window !== 'undefined') { posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, { - api_host: "https://app.posthog.com", + api_host: 'https://app.posthog.com', session_recording: { recordCrossOriginIframes: true, }, @@ -34,26 +34,46 @@ if (typeof window !== "undefined") { (window as any).posthog = posthog; } +function isBitgetWalletInstalled() { + // Check if the Bitget wallet is available on the window object + return typeof window !== "undefined" && window.bitget !== undefined; +} + export const availableConnectors = [ new InjectedConnector({ options: { id: "braavos", name: "Braavos" } }), new InjectedConnector({ options: { id: "argentX", name: "Argent X" } }), + new InjectedConnector({ + options: { + id: "bitkeep", + name: isBitgetWalletInstalled() + ? "Bitget Wallet" + : "Install Bitget Wallet", + icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAMAAAAKE/YAAAAC8VBMVEUAAACS3/uZ5/rF4v7h/f9K6Pdi0vtTyvqnt/5T1PlH9fakpf/p//88/fXs//5G8vXy//5F8/Zj1vqvuv514PrI7v2J3/o6/PTE7/1A9fbU+/3Y+/1R0vo7/fXh//7G5/3l//6jov6k8fuxs/6ppP6/1f5O6Pfv//7n//+bsv4AAADy//7r//7l//45//RE/vTg//1I/vVB/vRL+vXb//09//RM/vXV/v19/vhQ/vXw+/5z/fc++vVh+feC/fii/fpf9PdQ1Pmxr/6y/vuM/fm8/vun/frB/vyt/vpq9vi3/vtZ9fdRyvq1s/5N4Ph4/vfj9v6tqv7e+f1q/PbK//xS3fhS6fdl+/bX2f6c/fmS/flw6fng7/7f4/66uP5O2fmB8Pla5/jQ//2H/fjT1P6X/fl47vlV/vXLy/5p7fhc+fbHx/5v7/hL5vdR5fhT+fbP0P6T7/p15Pp09PhB9fWL7fp6+vhn8/di8PdG8Pbb3f7F/vyppf586Ppu/feK8/rX+v1W2PlN8vbW7/1Vzvrb8/6D6fpE+vXO+/zj6P5x+PjV9v1Qz/rn8/7m7v7Y4v5X7vfCwv+lo/7R6f3I/Py/vf9H9fbq+P7Q4f5Z0/qt9/ub8PuC9vlU4fiT9/rs/P5t4frQ2v4KGBje6f5+3/pI6/bCxv569Plj4/nY6P7B1P67wP7K5/2gtP2/+vzJ4P7P8v1c7vi2+PzL7/1b3/nJ0/5P7vdm3Ppj6fnEzf7Q9/2zuP6k9vtM7Peb9/qJ3fpf1vq5xv6TyfyO5fpd2/lZ/faG1vrC3P6rs/6k8PuU1fvJ2f6+y/6Xvvyl6Pu2vf633P205vxqeX5Cf32lxv2i2fyW3vuorP6hq/6+5P2wyf2u7fys3/zD6/2gvf2puv0dHiC40P2w1f2n0PyN0PuvwP667f2d0fybx/yi4ftYe3xPn5oufHvW7+5U7uZwm5oQLy3R3N5G29eEnp53zco5WF0qMC+dzM2MzcwsLzAtbGshLA3xAAAAKnRSTlMAIf47Wjuk17BXvH1536di36KDXdSkaIV1cd+/6c/v5s/Pvd/f0+PKk9+fkOCeAAAZMUlEQVR42szVvarqQBSGYS2iJHAQbUTIaXdpceo06cPuYruvIKnSpFQCaayFFCm9BbuAl3a+NWtmPojljj/vGqwfFkudTdo8DDeLdRTHcSqd43i5XCw2YRDMPrJwt/76Rj+2VNlalmXLxeaj5HN4i0LEVKORGkEefoR8tf66FtrYrerUsQmfvbVddDVZc2HJqk7xqBY24eF89p6CqK6VjKFa8ux0xGbv2Hewvdboatkm7hrDCxmreSevWTeXXEFMNRLyw67pJhvR/bp1r6IK1RXN7j6IBjtXM9VvZK/+VNaskY3ozsHWyHa9lE0y1byPK81ctardbb+FHUQkc9mebfPsPM8h98smvDk3WdbIWPbz/nLmUZIkVeLYY/Og7BKjZrJzzz5jGkFLmX4K/Vm/JLsKZKIfVj2I2ubMUEuy7KNlN7JtQdOdYeLNbPqCv4lk0CRfatswAD0UQ3HAFGVZfpdAO7NeyTHFOHfz0HLyZW8dmepLdbkY9CCjaoGbSqTL1o6C/jHmTp7UjdnTLntu16zkxJEVfbJwax6wa1WD3ZOtt30E24BBxqTPW/au2icJnoe3To2gxqNa4Hoiyu773CVoyV5J1ym689PEq6nWvN3v985c6aDWmq16GG7OfDgcQLb1YMuy/Zlg1H3EtiWoUSMPbSY6DTXjeXfbVq1ZNE/kdEODo+NEvLrslU042Z2Dd74pTiSoQJbx5ARisFtnPl3qExI2yCIHGpW+XnJiGdaNunf33//T7MyayQZZxphbr1Y21EjoBk24+UYqm3S6IfdifNzvvzxsPWdH/qfqSsxKtmrthufgh4Forptmkh37LmP7T2odu6YVRXEcJ0OyZWiQdO/YoSERBaGDu7uL01ssOkSQBrI4xMGsb+oWeIQMrsKjvlFo/pwOJdC933PPvfc8S4hX8zvn7h9+nLz4rsP+gNmrW4y4fc/e3WRomglxYjYmXrZns6XKfTYsZgns96oxawL6yszfIYtZFrapresIt659UDN3ZWA/s5vN3SZmvTk5/LNhPRN/0jHN+6aEsutkXlCTN9wUjpXcMQQ268jsunGoOUZqlqav6DlGwZ7c5Glyz+7L1tj9h7q6fCgpu6RuuDGwRUwOVEdzS4uGTO6Z0PMP557zME/mFsiv1y1uR9aVUHMZyevnNWSeqg++Z/3iyT0r+cKKdlUrG+nLn8c38xc15MyNk4dE9VrGsvddf7SeIX+5avmeL0Cz2rS6Vf3y+PnNYCbX2XXmyaCj25N5IcW6KIqTvc2mjrdxISO5vES9lSRzxogad5aVGVqre/nM1MmiPtvr/6CJGaJmF4rGHLUTZj5JMgc3ZNioiReX61LRSwYz43Kebj6u9UwwG5qaKfpSvObeac6dOO/D5IWUzDKDvHT5XzwtptPj5I/dqRVtZn8Z96iJiRPMv3NJn8lYczszD7WcB5GuixDM06ejw8yADe3Jiu6oO8X8E7FLxtazxGwpIFvNksY+f4Rm9mo7DSsadWrPBDZkHmNkpm5mt9Vn+/4RuhiZqLnd9uZOp5NstqZXwVyJm2QBzRiYPE2fZucJx/HVzAFtaiWDFna705nsNvfy3rxXZ68kwBHzmKpassuqkBm4mYbMZrOEsz599Z6NTMSMuA06wdxDLWPmHLJnV2r2U4zUPB0oGDLmRSPxoFuvoaMYbpNJMhN1s4AtFQM5BHEwDzCzkCWLxeJsx3HEg3bk7YOOZlVPks2531Uvp+Y6O2ZQVaPRCK+QGcjBTI72PQ4j180k2UyUzHTVe6vm6B6xA8w+8TYwSxppXzszR7I8I+9nzru9XlfN3VtlS6qKlYpDzSNtejgd1sg3i5vzlC+HS71myO0t9Lfd5vF4HNBdIXeFLF7EIag9mfVVDweQTY355tfRzt/QLUMrmYEM18hJZtTjgCarrkSsuFnIoxhqDuShkQUt+bTjd1JL0du3gTmKnfofZ3YP2lQUhnE8fg0WURz8RgcHcRQVWgmxgrUaHRwiSOsSlCpKQEJoKoiNk0hmp6Zk7ebg5BDcHDp3dHdQUREFcfL/vu+5901yYu6NzznnJoPDz4dzr/HcfGaiXruYWVaah0wz12p3akY2c/VZ9UVVa6bo5fqOjLtwuGeCeFKziYM8IpvYi67VHtaImoPaatbU6/V/VL0viGMzPbsZ8YMHX7/nMZf0cqlELjmbGdwrJJjpmXG/ZmTM1aoVbWhycEzRTo57NnJOMwHLErIMy4qNJwwhMxBbEIeYGHAgk872kUUrOTKL2sk5zaU4Tge+wkStcTLDyPfZz2S5mpo79U7n4KiiI7MX7eQLE5tdzHVFhplTctnMjUYjrVmbdjNosn1k0SaOzRP3fLt0m8lFpgWsLcyeMgnkGmQzB7WR3UzVcdFoxz82aDmX+TZRMrGvTF18L5dLKZiotwx5uOjWckvJbt7cjB4gu+OWHX0BM+jc5jjYWWUmbKanDZmoeYNUA7q6LG43g94cflZP9ZvxMoe3M3n//j/N5i0p0821djmA2RyNjY37G1VDt1rVFqnXmfWEHFW9LXh9a7i5r+ac5gqzwuTCNyOzTOvsdrvdaDdCMHvRgOlZyd1O512n00MdVb3/7Nn4h2j/wzlfz1sYoyi+XAFf4VpmBHGZ0Q5iM1P0OmlpMBMzK3lt8/DgbegtR0+N/D1/Vh9rZNCqebVcUa8XDdmyvl6tJuZuq1tnYH63Kere5trah239z7uoZcj/ZSar/zQbeVU6TqNoE2NOySbuYiY9yZqkf39MGVrFkTmQs82rq5XVJBUmST+5shiQ2ywnY24yEIckPSPGLEX3pObNtQ9rayfdvM2OjywDZMT5zZlpsxBXEnKz2WywRKxmJ4dYzV70mzfb+ndHAAdyVHNOc3G1WAxrlJlhNRdlNlFjpmXcqfkeZBlha3R6plYy6MO+O0wc7YyJzMXRaetfoCjL/oiRCUWHgHVyay6p2YtGLGji+yOYnWyHGupFnMdczBfAem2m4g3MzfWLjHsM0ETJ8d5A7ftjt6HdDFkyqfkmgysfOkanKcOjZDUreW5urmtNYyZ95JCdAb1fxedkndOe5Yzu8YPHmEles2GHUmSmfvmq5ptOvti8SIRMMLfmTOxm0K5+/fpA8i8L5nMyEjNBnJhLmWZo2WkyBsEzDMBSs4jVvNjtMpfemXm215vvJ4P+aOZd6RsUTkKlY5LW/Oj9o0xz6pqxi82ZmWE1PevAPTOjYswSyLClZsQEtPfsZsjENvU+E4cDZxWzgjhHzwEK818RsS0r2MQh66lYyKoGvPBudhbxvBVtZIs99Kb6Xp+EUDNkTelPlnkAe1HXuGjBM2hZ1jKZs1twcdFqBi3m+V4wX1a0sQ/Yljaxk98/puJALv3IMA+Kocjgc4wf6hCZKBnx0hLiBcyz1Owb+rqJX716elTRVrKbHz1OyWQrp1mpcVztf4jpYsiaxbnF7uIi5CXIbI7eLGRVa82Yg/qVbOpdSmYmZLSJmd/uX8aZf5lkOFtj8zkVO7mLGLJExAQz5GuYL1/2niU7QR9Sq5NvGFjIgs5o2sVGkXnv5/gzs617LmbSMgMyaMi2NdgckK8Z+fqVK1cwX70q5pdyJ06l4htiTtBEzJWvGXs65fYln9r3BUlqRp3UDPka5MvXQav5qZpfyp14PCVzJAvZxGYu88M96+nh3EnUaGMzYjdDxgz5zaD5pdyJwWtmI9tZUNnS/pWh9s3pyVSnZhdDJr6dzWx7g63xFDRmwn0oaGI1c4ycklfM3M5U442TpXaxmyGnPbvZezb03W2FXTc8lywlEwcyydohSWsTqcFavOXYHNBX3Xx3enpnYZ+LA3mFBLKgG2QrS83OjJJH7befk4mSid+DtqHJtKCPDHQsR8eEoiE3ZGg2stQKmFSN2MxxzYa2nq+a+ZbUfHea7CnsV3KiHjgcVHOzIf9VzqNWg37KWMqhVnFE9prNrPcgaDOT04X9/l4BrQVwuZEknKJkqZdGJUvtYt8Zkth8y83n9xam4PpLEEkNMoev/WTQ+dULE6jHkUebn0+fP1aYMq6TV2rEWyZCzlZ/40fDQpQM9SfIlpTsNQczaMxe9PmjhVOJ+Im8s3loZCbu+3jt8JXjqtZ6K1MNe2J1AHvNI8237pJgPnO08NbM6eu8/hcgcmBMkhO2TLV1thA+mIwcahc7uc98i7xMzeREIZBFXTNzeMkEuRoiR9z51D20HqNnqQfIXjNmEvVM02cK2rHX7OaY3G3V83Q9bxfP7ww15OgGFLMXPWAG/TbdGbyeZrg5qJfl7QfH8jLqWeqf88PJo453BmTvWYt2M+i/zdxfaJVlHMDxczwRi8WIypCELCr64wZdCIGXhRRBMKULoSkRXgjdWNBNKQhjkgyTWqHUxTKH4PxLsgvbxZiC1rSp4dTN6dp06oKQiP7d9f39fs/z/s7x0b3vdFbf5zlnXX749Zyje9+3TPymiDUFr6sma+++j5grxjnqr//CmXb9+Ry1ilMy5nTOoAM5iFmIWes22K1pzIEcb5z+nKN+1bNPWBF1aoacnmdDN5VcDPi9+EjLWxsswI5WcxH1F7Jry1PDTcaM2NBOVnNT6Su08XmndWxtwzoVZ2ZNxQXV/Pt+9eWZqWvJZGQ3k5kfKz38ZnioDPJ7Rnaz7MNOjnf08tTy2yivqgqo8Ro5NVOVuekB0ORP76k5dBgxZllz3p3z5ZxALqDGbKu6PHVCTs2Gfrz0MNgoxtwFWcEbDhNmEZOKBzEXU38jv4++zHoNLa9C6kCexgyZ5pfu5WFUM3chxtzF6gtowmxznjOI+vtBxOe/P38+R/0naDarujx1Qk7nTI0NoKUuIVuIMYfmxCAPDmJmCTpfvYReW4J0Jmonuxmym7WG0oNmZpFOua/v8Ia+YGYZWZaqB8+LuZB675KkHPWPJnZyaqb7S49ARhxC3NV3OJDPAM7MGmTQVES9d+8SNvSZqJ2MWbvR3Nh4T+k+qBlZlgYc85kzZ4KZbeZMfeX8lXy19QotWcKL9Uq+GnJynN0MulIq15jNywLMnsOamjM1NTU4FQZNgUx56lf2sjR+xPLOdUJ2tJIFXcrImENn+hCTqCGTmU8Nnh89f0rWqSuw89UfutX7Lec/LwlkSsiYiWt5D3d1Hek6coRXlZmtTZ0BbJMeZaFGDBkxFVDfrBz1z3DNrOpAdvPjoO/FDLnvyJE+WQO2jBzVNDqKehQwoT5VUP1Z6MPPPmQXU/+i4lD1mA3dAPoRnbIk7r4gNvXQ1JCJp1SsZidfuHLhwoUCau8NW29Mr76uZrabg1q7H/R9R7wBWZqoh84MDQl7CDXVqIevDAf19R+n6/pHGbi636f9BjFvejasSoky8uVq8tAAYsJsZEcPnxoeBg04dCBrP32rfVQbFw/5ndrEuZN2cWKmsqAfDmLWZdQx1EyaRlnRfJKFGjJmtohZkXwAMuiMfQP9bfYbrJwz/WtqbozmhY0PlKR7VSwNXB5gS1chx6L55OhJFVtGNrVPWcxx1MLmvcos8Z737ZGQlR3VDYp+BDLbCuaBqzR0NZgnRicmMDNmOnXS1budDDqwvaC3y8oBnWv+45f0ZDi5ceE9ii5DDeBDA2zxBvMl1oSaJzA7WdG7h3df2E0HdpuZFN2Ou729HS5v6+lFCbaVa4acjNnNCyslLUMfGjjEjupLVzGbuoo9LIv2DWNmHVB1x4GO/a1CRpu1Xl7f6voIue58M9hkzk5eyJHW7oV7+ZB1VVZUaxOXAEOeOGkdPTl8dPiomDXIHR1x0q2trcJ29fqqIM/EXDNlZzcE9COihR3d165dvSZktqNRB/NRaXjfvn3D+wK7g1o7Wgl2e6ura8nFzYjT08wijrSFNHTt0DXCfFrItaMeh9wN+SSDPoo4kAHTfsxk5ra2NkcvC+pFi9YvKmRG7FVN2SpHdF2N2Tp9+tLpKvP4BObx7jhp1IR76+4O2NmoqQ0zW1rftgxzRi5irhU3OTmM+hm4fj5+YEFmq5hRq3rk0sjIxAhmFuzu7m4zG5lhb8Vr6GBuYxPw9mWYswqZU3Ekk58ODTDrGkvJbA31CGpBW92oa9hbSdk7O3buVDM7qJfJRhvpueZqcC05PR1U9wOpmi3koBayND5i5nEhC/pYNXkrZMygqcXECvYKml3tOZgaSl5FyNq1ixcvIg5mtobZ1biPHT12bN+xfVsjGzLmnTtbBC2rWd3NMzKnU/aj0einw4OrXfwBNBkaMhuyq9WMmhBbHA2rZTtg24iblzXzxntxc/S6mOVk/ZPFq3e1mtljp09Pwrb6R8b7x8ePj3cfVzZkVW8z9ebNQt7Obtneqmp1I4Ysu/l6AbPP2Mm8LD8dXrlKbOrTqCcnJ0cmzQwZNGSpR9XbyMhRTS2h5pZmduzH6c1/Y47c9GBkbv8Y+kdxD+rIHrs4NjY2ORnZoPv7jx8fPw77IOieHlOTqimgmbWrC5sbbyo2the+pL2KoPewzcwCDVvV/dJ4/3E6ePBg98EeKYwatZpRuxkyrzswkzmd7R9Drw4waroIXMyZun+SSRNqYZOYeyCLejloCbGqrU0tzZs2NW+audnmm/3w/GPoPaTgoBaxqiP7hJpVbWYyNOblQb3C1BtNjZlXYbOT2Wk26KTnXD3GZtQk5hOTJzBrhs7YqzBvWx7MK9S8kVF/bEGm4mafrX/PJYNORu19NzbGFnOvqGHv6u/fJaPekaHXbFsFehvoDzZ/gFnavpFAs/EWNgM0p67ig/ZRQ9aXsHvZmGXUsIHv2LHD0KtRg16FGTVoIa9YsVHYH4eKmF3sXFfnDFpHfe7cHhZiWcS8e+mEtIvEfNzYq3vWrFmzigS9GTVoEnNLVOeaf/qlk1zs8IKDpjrEdPbcnrOqpt7vxlQd2TskMa9eDbpHZi2TphXKxkxBnWv+tVM0vCXggoOmCmKd9VmGHYLss47qd95RtI06U2tmtnLNgEW6OAesVaIyHbWGWgpqcxv6k4jWUUc0KdrVxcyLhcsb4NwaSreq/JyizzJqzFGt2ahhfyJoRu1qRZOSQUvFzExad36d5dItq4dMYdLsl1hx1CtXKvoTR5OjUUtK3ljMrLtQ95em6YlgDmzp9d5edu/KlTrqz3NGDZqKno2UXfxT6JXPnfuUBdhi0uZGTaBRY3Z1NdrV+WYjF6xcmrb6T0HrsZaWLpUD8tLrr+usCbSrk1HPxKyTLibncOT0xKfa2rVrMaN+iURtaMygUaejdnURM2j2nR0Or4z4HOssatiobda0UhI1+aiDWsiCLng2bNGdfXP4AametagpUdd+Fn3SVGzO8XTc+eFwNWZamqgx0y1GzawLmouK6VlTFT3WpGZXZ8eabnaqi5r9TOf2QNH/Yd5Tho5qR7s6QZu6oNkmPYtmqkCWMKN29i1PNRn698JzLsiulApXH9HCdjSlXyA+6qJm71bsxrj5EN6WGjSZmQSdqo2db95SDWbdDGxeNxev7gZ1NmlXJ+hc8xbM7KpqsLZ80v7FUVzt6PSz6Gg/IL//NG1/Z+ZOH7WxjVmLp4bSjHvC1QVONSV/Q/XfFWmLJlafNvyFnUHsk46/nnMR7PbUjna1oikZNTlaq0FvWqxsyJ0WZDY8FbO1+A/PlEu3pwbtakenp9rQqdrMgb1pi8y4083yhje5HCY9jvm2qnN0jbr2b3vT/AoDmjKzBV3Njfam4KSG0m1X72rMS6cdNTnaLoGwHY3axJhRq3gx5oWzZnb12vRrr3bUhvZR20WyeOHG0GHWMcxBzb5ZfD/fmdrRPmlydDLqzYaOlyNbIPuslcwW8c3J8lzmHVZ5CrWjXV2LJrmy5+rtoIXdImwRN7OzSbOaOpswp+KmxyqlO678lKLXJucjHbWiN4NWNWy/xq7s5i0sRQNm+72hqtuefG3MRvXT/AGzA7Wjw5VfbSdk0kmjxqyBbrJpZ0wyvuxnMc+a+uZf1Y6mbYZebmZSNGy7bWRqG7Y6t7g29pgf51k4Io5WtV8DQQ26Z/WaHg61thV1YNfeows16drCWxJHYzard7Wjd4EmLkfCXiN3YfSGBvcVIRu6FbWa2R5ezMZ+gc2SHz7mWRt2Ldqv7NlF1J4eY9tNOj8gLa3b2/TOc5vdwQ2r+YWmZTDBsq3HK6XZr77qfPRGdbzK7ve7YKM2tA1bxHqTHy3bQrmMlcVpviuV6yKai2SKRm1oZ2NWdYeiW3e2arBrH6fAG+BWQ7l0t6o86eiqWwOZ2W+Yo8ZMasYb1J6RFy1iL5pfKd3NHoKdol3NswmgpQ6KaDJzxl4ki+Tt0XtKdznYoCneOurnjlc1Okxan7eBHc2o21k8gwVZXia++2RnO9pv43ZjpvjADWrY/hRZfFQPd0jI8+862dl1UQ16h6kdjVrNZOb9bkZdFWf536xc9+SNo+bpFTWjpvCYIfE0p6x2czv7sfv/RbKPG7Tf5+8WtLCzxwxhA0ettbOtMORy6T+p/FBdLVrMR33SvPTh2aiWp5O/lebP+2/EPu+nFd1t+ROdiDEfYEU1aHp07j3l0n9fBbijpYim6ifCAc/7P4BdPm/u06DNjDqSQWsL5s77X0w4rVJ5aN7cuU8/vWABo+Y59gULFsyfi3aWuf8ADHnSl6eWnZwAAAAASUVORK5CYII=", + }, + }), + new InjectedConnector({ options: { id: "okxwallet", name: "Okx Wallet" } }), + new WebWalletConnector({ url: - getCurrentNetwork() === "TESTNET" - ? "https://web.hydrogen.argent47.net" - : "https://web.argent.xyz/", + getCurrentNetwork() === 'TESTNET' + ? 'https://web.hydrogen.argent47.net' + : 'https://web.argent.xyz/', }), - new ArgentMobileConnector({ - dappName: "Starknet Quest", - url: process.env.NEXT_PUBLIC_APP_LINK as string, - chainId: constants.NetworkName.SN_MAIN, - icons: ["https://starknet.quest/visuals/starknetquestLogo.svg"], + ArgentMobileConnector.init({ + options: { + dappName: "Starknet Quest", + url: process.env.NEXT_PUBLIC_APP_LINK as string, + chainId: constants.NetworkName.SN_MAIN, + icons: ["https://starknet.quest/visuals/starknetquestLogo.svg"], + }, }), + + new InjectedConnector({ options: { id: 'keplr', name: 'Keplr' } }), ]; export function Providers({ children }: { children: React.ReactNode }) { const network = getCurrentNetwork(); - const chains = [network === "TESTNET" ? sepolia : mainnet]; + const chains = [network === 'TESTNET' ? sepolia : mainnet]; const provider = jsonRpcProvider({ // eslint-disable-next-line @typescript-eslint/no-unused-vars rpc: (_chain: Chain) => ({ @@ -64,33 +84,33 @@ export function Providers({ children }: { children: React.ReactNode }) { const theme = createTheme({ palette: { primary: { - main: "#6affaf", - light: "#5ce3fe", + main: '#6affaf', + light: '#5ce3fe', }, secondary: { - main: "#f4faff", - dark: "#eae0d5", + main: '#f4faff', + dark: '#eae0d5', }, background: { - default: "#29282b", + default: '#29282b', }, }, components: { MuiTabs: { styleOverrides: { root: { - "& .MuiTabs-flexContainer": { - display: "flex", - flexDirection: "column", // For mobile versions - alignItems: "center", - ["@media (min-width:768px)"]: { - flexDirection: "row", // For desktop versions + '& .MuiTabs-flexContainer': { + display: 'flex', + flexDirection: 'column', // For mobile versions + alignItems: 'center', + ['@media (min-width:768px)']: { + flexDirection: 'row', // For desktop versions }, }, }, // Overrides the styles for the selected tab indicator indicator: { - backgroundColor: "transparent", + backgroundColor: 'transparent', }, }, }, @@ -98,14 +118,14 @@ export function Providers({ children }: { children: React.ReactNode }) { styleOverrides: { // Overrides the styles for unselected tabs root: { - color: "#E1DCEA", // Text color for unselected tabs - width: "100%", - ["@media (min-width:768px)"]: { - width: "fit-content", + color: '#E1DCEA', // Text color for unselected tabs + width: '100%', + ['@media (min-width:768px)']: { + width: 'fit-content', }, - "&.Mui-selected": { - color: "#000", // Text color for the selected tab - backgroundColor: "#fff", // Background of the selected tab + '&.Mui-selected': { + color: '#000', // Text color for the selected tab + backgroundColor: '#fff', // Background of the selected tab }, }, }, @@ -125,7 +145,7 @@ export function Providers({ children }: { children: React.ReactNode }) { - {children} + {children} @@ -133,4 +153,4 @@ export function Providers({ children }: { children: React.ReactNode }) { ); -} +} \ No newline at end of file diff --git a/app/quest/[questPage]/quest.tsx b/app/quest/[questPage]/quest.tsx index fce58c6f..82226f77 100644 --- a/app/quest/[questPage]/quest.tsx +++ b/app/quest/[questPage]/quest.tsx @@ -54,8 +54,7 @@ const Quest: FunctionComponent = ({ } }, []); - // this fetches quest data - useEffect(() => { + const fetchQuestData = useCallback(async () => { getQuestById(questId) .then((data) => { if (!data) { @@ -79,6 +78,11 @@ const Quest: FunctionComponent = ({ }); }, [questId]); + useEffect(() => { + if (!questId) return; + fetchQuestData(); + }, [questId]); + useEffect(() => { // dont log if questId is not present if (!questId) return; @@ -147,6 +151,7 @@ const Quest: FunctionComponent = ({ setShowDomainPopup={setShowDomainPopup} hasRootDomain={hasRootDomain} hasNftReward={hasNftReward} + fetchQuestData={fetchQuestData} />
diff --git a/components/UI/avatar.tsx b/components/UI/avatar.tsx index 61ae9b90..2f6e0f16 100644 --- a/components/UI/avatar.tsx +++ b/components/UI/avatar.tsx @@ -1,7 +1,13 @@ -import React, { FunctionComponent } from "react"; +import React, { + FunctionComponent, + useContext, + useEffect, + useState, +} from "react"; import ProfilIcon from "@components/UI/iconsComponents/icons/profilIcon"; import theme from "@styles/theme"; -import { useStarkProfile } from "@starknet-react/core"; +import { StarknetIdJsContext } from "@context/StarknetIdJsProvider"; +import { StarkProfile } from "starknetid.js"; type AvatarProps = { address: string; @@ -9,17 +15,24 @@ type AvatarProps = { }; const Avatar: FunctionComponent = ({ address, width = "32" }) => { - const { data: profileData } = useStarkProfile({ address }); - + const { starknetIdNavigator } = useContext(StarknetIdJsContext); + const [profile, setProfile] = useState(null); + useEffect(() => { + if (!starknetIdNavigator) return; + starknetIdNavigator.getProfileData(address).then((profile) => { + setProfile(profile); + }); + }, [starknetIdNavigator, address]); return ( <> - {profileData?.profilePicture ? ( + {profile?.profilePicture ? ( {`${profile?.name?.length ) : ( diff --git a/components/UI/changeWallet.tsx b/components/UI/changeWallet.tsx index b35c14a1..966cd973 100644 --- a/components/UI/changeWallet.tsx +++ b/components/UI/changeWallet.tsx @@ -10,6 +10,10 @@ import useGetDiscoveryWallets from "@hooks/useGetDiscoveryWallets"; import Typography from "./typography/typography"; import { TEXT_TYPE } from "@constants/typography"; +// Define an array of wallet IDs with explicit types +const INSTALLABLE_WALLETS = ["braavos", "argentX", "bitkeep"] as const; +type WalletId = typeof INSTALLABLE_WALLETS[number]; + type ChangeWalletProps = { closeWallet: () => void; hasWallet: boolean; @@ -53,7 +57,13 @@ const ChangeWallet: FunctionComponent = ({ > - Change wallet + + Change wallet + {connectors.map((connector) => { if (connector.available()) { return ( @@ -61,8 +71,7 @@ const ChangeWallet: FunctionComponent = ({
); }; + export default ChangeWallet; diff --git a/components/UI/iconsComponents/icons/eyeIcon.tsx b/components/UI/iconsComponents/icons/eyeIcon.tsx new file mode 100644 index 00000000..dfcbd537 --- /dev/null +++ b/components/UI/iconsComponents/icons/eyeIcon.tsx @@ -0,0 +1,54 @@ +import React, { FunctionComponent } from "react"; + +interface IconProps { + color?: string; + width?: number; +} + +const EyeIcon: FunctionComponent = ({ + color = "#E1DCEA", + width = 17, +}) => { + return ( + + + + + ); +}; + +const EyeIconSlashed: FunctionComponent = ({ + color = "#E1DCEA", + width = 17, +}) => { + return ( + + + + ); +}; + +export { EyeIcon, EyeIconSlashed }; diff --git a/components/UI/iconsComponents/icons/walletIcons.tsx b/components/UI/iconsComponents/icons/walletIcons.tsx index 89a4552e..efc72565 100644 --- a/components/UI/iconsComponents/icons/walletIcons.tsx +++ b/components/UI/iconsComponents/icons/walletIcons.tsx @@ -33,6 +33,59 @@ const WalletIcons: FunctionComponent = ({ id }) => { ); + if (id === "bitkeep") + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + + return ( { const currentNetwork = getCurrentNetwork(); @@ -53,10 +59,11 @@ const Navbar: FunctionComponent = () => { linkText: "", }, ]); + const sortedConnectors = sortConnectors(availableConnectors); + const { starknetkitConnectModal } = useStarknetkitConnectModal({ - connectors: availableConnectors, + connectors: sortedConnectors as any, }); - const fetchAndUpdateNotifications = async () => { if (!address) return; const res = await getPendingBoostClaims(hexToDecimal(address)); @@ -94,7 +101,7 @@ const Navbar: FunctionComponent = () => { const connector = availableConnectors.find( (item) => item.id === connectordId ); - await connectAsync({ connector }); + await connectAsync({ connector: connector as Connector }); } }; connectToStarknet(); @@ -121,7 +128,7 @@ const Navbar: FunctionComponent = () => { if (!connector) { return; } - await connectAsync({ connector }); + await connectAsync({ connector: connector as Connector }); localStorage.setItem("SQ-connectedWallet", connector.id); }; @@ -174,18 +181,24 @@ const Navbar: FunctionComponent = () => { return ( <> -