From 9601678552a7c2b73ae01d2fc0545c7958382f39 Mon Sep 17 00:00:00 2001 From: jnsdls Date: Thu, 13 Jun 2024 16:48:34 +0000 Subject: [PATCH 01/10] feat: compare contractAddress and chainId in PrimaryDashboardButton component (#2655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes: DASH-43 --- ## PR-Codex overview This PR enhances address and chainId comparison in two components to ensure accurate data retrieval. ### Detailed summary - Updated address and chainId comparison logic in `primary-dashboard-button.tsx` - Improved address and chainId comparison in `modal.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../contract-components/import-contract/modal.tsx | 6 +++++- src/contract-ui/components/primary-dashboard-button.tsx | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/contract-components/import-contract/modal.tsx b/src/components/contract-components/import-contract/modal.tsx index 68ce23be97..643a1581e6 100644 --- a/src/components/contract-components/import-contract/modal.tsx +++ b/src/components/contract-components/import-contract/modal.tsx @@ -94,7 +94,11 @@ export const ImportModal: React.FC = (props) => { registry.isFetched && registry.data?.find( (c) => - c.address.toLowerCase() === contractAddress.toLowerCase(), + contractAddress && + // compare address... + c.address.toLowerCase() === contractAddress.toLowerCase() && + // ... and chainId + c.chainId === chainId, ) && registry.isSuccess; diff --git a/src/contract-ui/components/primary-dashboard-button.tsx b/src/contract-ui/components/primary-dashboard-button.tsx index ce3a96161b..da4e0e2119 100644 --- a/src/contract-ui/components/primary-dashboard-button.tsx +++ b/src/contract-ui/components/primary-dashboard-button.tsx @@ -39,7 +39,10 @@ export const PrimaryDashboardButton: React.FC = ({ registry.data?.find( (c) => contractAddress && - c.address.toLowerCase() === contractAddress.toLowerCase(), + // compare address... + c.address.toLowerCase() === contractAddress.toLowerCase() && + // ... and chainId + c.chainId === chain?.chainId, ) && registry.isSuccess; From bf7d1da17205416f52f61f71eec2ead329fb87d0 Mon Sep 17 00:00:00 2001 From: kien-ngo <26052673+kien-ngo@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:00:34 +0000 Subject: [PATCH 02/10] Fix Engine UI crash when `chain` is undefined | DASH-42 (#2654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR ensures optional chaining is used to access properties in various components, preventing errors when properties are undefined. ### Detailed summary - Added optional chaining to access `chain` properties in multiple components - Ensured safe access to `chain.explorers` and `chain.icon` properties > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../contract-subscription/contract-subscriptions-table.tsx | 2 +- src/components/engine/overview/backend-wallets-table.tsx | 2 +- src/components/engine/relayer/relayers-table.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/engine/contract-subscription/contract-subscriptions-table.tsx b/src/components/engine/contract-subscription/contract-subscriptions-table.tsx index 1f354172fb..9866ab0025 100644 --- a/src/components/engine/contract-subscription/contract-subscriptions-table.tsx +++ b/src/components/engine/contract-subscription/contract-subscriptions-table.tsx @@ -290,7 +290,7 @@ const RemoveModal = ({ Chain - + {chain.name} diff --git a/src/components/engine/overview/backend-wallets-table.tsx b/src/components/engine/overview/backend-wallets-table.tsx index 321148b3ea..73fd7f786b 100644 --- a/src/components/engine/overview/backend-wallets-table.tsx +++ b/src/components/engine/overview/backend-wallets-table.tsx @@ -141,7 +141,7 @@ const BackendWalletBalanceCell: React.FC = ({ ); - const explorer = chain.explorers?.[0]; + const explorer = chain?.explorers?.[0]; if (!explorer) { return balanceComponent; } diff --git a/src/components/engine/relayer/relayers-table.tsx b/src/components/engine/relayer/relayers-table.tsx index a47755c255..1dc2451a8a 100644 --- a/src/components/engine/relayer/relayers-table.tsx +++ b/src/components/engine/relayer/relayers-table.tsx @@ -71,7 +71,7 @@ export const RelayersTable: React.FC = ({ const chain = chainIdToChainRecord[parseInt(cell.getValue())]; return ( - + {chain.name} ); @@ -83,7 +83,7 @@ export const RelayersTable: React.FC = ({ const { chainId, backendWalletAddress } = cell.row.original; const chain = chainIdToChainRecord[parseInt(chainId)]; - const explorer = chain.explorers?.[0]; + const explorer = chain?.explorers?.[0]; if (!explorer) { return backendWalletAddress; } From 11ec9def78a2358c236b2ed623349e21e27ed455 Mon Sep 17 00:00:00 2001 From: jnsdls Date: Fri, 14 Jun 2024 00:14:02 +0000 Subject: [PATCH 03/10] feat: add eslint rule to warn against useEffect (#2656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR adds a new ESLint rule to warn against using `useEffect` and suggests using `useQuery` for async functions instead. ### Detailed summary - Added ESLint rule to warn against using `useEffect` for async functions - Suggests using `useQuery` instead for async operations > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .eslintrc.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index eccb3a2cd7..d7eddc61ce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -99,6 +99,14 @@ module.exports = { "inclusive-language/use-inclusive-words": "error", // turn off deprecated things? "react/react-in-jsx-scope": "off", + "no-restricted-syntax": [ + "warn", + { + selector: "CallExpression[callee.name='useEffect']", + message: + 'Are you *sure* you need to use "useEffect" here? If you loading any async function prefer using "useQuery".', + }, + ], "no-restricted-imports": [ "error", { From c4a382185d3a6c300220a3d7c20f4a8c462cdaeb Mon Sep 17 00:00:00 2001 From: jnsdls Date: Fri, 14 Jun 2024 00:14:13 +0000 Subject: [PATCH 04/10] refactor: remove useEffect wherever possible (#2657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR focuses on addressing code quality issues related to `useEffect` usage. ### Detailed summary - Added comments for legitimate use cases - Added `TODO` and `FIXME` comments for tracking and reworking code logic - Removed unnecessary files and imports > The following files were skipped due to too many changes: `src/contract-ui/tabs/shared-components/marketplace-table.tsx`, `src/components/settings/Account/Billing/ConnectWallet.tsx`, `src/@3rdweb-sdk/react/hooks/useApi.ts`, `src/components/custom-contract/contract-header/contract-metadata.tsx`, `src/app/components/posthog-pageview.tsx`, `src/components/contract-components/tables/deployed-contracts.tsx`, `src/contract-ui/tabs/events/page.tsx`, `src/components/inputs/BasisPointsInput.tsx`, `src/core-ui/sidebar/tunnel.tsx`, `src/contract-ui/tabs/code/page.tsx`, `src/@/components/ui/tabs.tsx`, `src/components/connect/Carousel.tsx`, `src/components/share-buttom/index.tsx`, `src/contract-ui/tabs/analytics/page.tsx`, `src/components/contract-components/contract-publish-form/custom-factory.tsx`, `src/components/onboarding/index.tsx`, `src/@/components/ui/ScrollShadow/ScrollShadow.tsx`, `src/contract-ui/tabs/permissions/page.tsx`, `src/pages/dashboard/settings/billing.tsx`, `src/contract-ui/tabs/overview/components/PermissionsTable.tsx`, `src/contract-ui/tabs/payments/page.tsx`, `src/tw-components/code-block.tsx`, `src/tw-components/AddressCopyButton.tsx`, `src/contract-ui/tabs/embed/components/embed-setup.tsx`, `src/components/delayed-display/delayed-display.tsx`, `src/app/components/Header/MobileBurgerMenu.tsx`, `src/contract-ui/tabs/overview/components/LatestEvents.tsx`, `src/contract-ui/tabs/events/components/events-feed.tsx`, `src/contexts/configured-chains.tsx`, `src/components/wallets/PosthogIdentifier.tsx`, `src/contract-ui/components/solidity-inputs/address-input.tsx`, `src/contexts/error-handler.tsx`, `src/components/configure-networks/ConfigureNetworkForm.tsx`, `src/tw-components/button.tsx`, `src/app/(dashboard)/(chain)/chainlist/components/client/search.tsx`, `src/components/homepage/CodeBlock/index.tsx`, `src/components/mission/HowSection.tsx`, `src/components/contract-components/contract-publish-form/index.tsx`, `src/pages/dashboard/connect/playground.tsx`, `src/pages/_app.tsx`, `src/components/hackathon/common/Timer.tsx`, `src/pages/dashboard/settings/api-keys/[id].tsx`, `src/pages/cli/login.tsx`, `src/components/hackathon/Timer.tsx`, `src/pages/dashboard/connect/account-abstraction.tsx`, `src/components/engine/overview/backend-wallets-table.tsx`, `src/components/cmd-k-search/index.tsx`, `src/pages/dashboard/connect/in-app-wallets.tsx`, `src/pages/dashboard/connect/analytics.tsx`, `src/components/engine/contract-subscription/contract-subscriptions-table.tsx`, `src/pages/dashboard/connect/pay.tsx`, `src/components/wallets/ConnectWalletPlayground/Playground.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- redirects.js | 10 + .../ui/ScrollShadow/ScrollShadow.tsx | 4 +- src/@/components/ui/tabs.tsx | 4 +- .../react/hooks/useActiveChainId.tsx | 2 + src/@3rdweb-sdk/react/hooks/useApi.ts | 3 +- .../chainlist/components/client/search.tsx | 20 +- .../components/Header/MobileBurgerMenu.tsx | 6 +- src/app/components/posthog-pageview.tsx | 2 + src/app/global-error.tsx | 2 + src/components/ClientOnly/ClientOnly.tsx | 2 + src/components/analytics/area-chart.tsx | 3 + src/components/app-layouts/app.tsx | 2 + src/components/app-layouts/provider-setup.tsx | 2 + src/components/app-layouts/providers.tsx | 2 + src/components/buttons/MismatchButton.tsx | 2 + src/components/buttons/TransactionButton.tsx | 3 + src/components/cmd-k-search/index.tsx | 10 +- .../ConfigureNetworkForm.tsx | 14 +- src/components/connect/Carousel.tsx | 4 + .../contract-deploy-form/index.tsx | 2 + .../contract-publish-form/custom-factory.tsx | 4 + .../external-links-fieldset.tsx | 2 + .../contract-publish-form/index.tsx | 6 +- .../tables/deployed-contracts.tsx | 3 + .../interactive-abi-function.tsx | 2 + .../forms/properties.shared.tsx | 2 + .../contract-header/contract-metadata.tsx | 2 + .../delayed-display/delayed-display.tsx | 20 +- .../embedded-wallets/Configure/index.tsx | 3 + .../contract-subscriptions-table.tsx | 44 ++-- .../engine/overview/backend-wallets-table.tsx | 34 ++- src/components/hackathon/Timer.tsx | 49 +++-- src/components/hackathon/common/Timer.tsx | 40 ++-- src/components/help/contact-support-modal.tsx | 2 + src/components/homepage/CodeBlock/index.tsx | 12 +- .../homepage/sections/UpgradeModal.tsx | 2 + src/components/inputs/BasisPointsInput.tsx | 2 + .../landing-pages/image-section-item.tsx | 1 - src/components/mission/HowSection.tsx | 34 +-- .../onboarding/ApplyForOpCreditsForm.tsx | 2 + .../onboarding/ApplyForOpCreditsModal.tsx | 2 + src/components/onboarding/Steps.tsx | 2 + src/components/onboarding/index.tsx | 4 + .../product-pages/common/nav/HoverMenu.tsx | 2 + .../common/nav/NestedHoverMenu.tsx | 2 + .../selects/NetworkSelectorButton.tsx | 2 + .../Account/Billing/ConnectWallet.tsx | 2 + .../settings/Account/Billing/index.tsx | 2 + src/components/share-buttom/index.tsx | 9 +- src/components/shared/BigNumberInput.tsx | 3 + src/components/shared/ProgressBar.tsx | 4 + .../ConnectWalletPlayground/Playground.tsx | 207 +++++++++--------- src/components/wallets/PosthogIdentifier.tsx | 8 + src/contexts/configured-chains.tsx | 6 + src/contexts/error-handler.tsx | 18 +- .../solidity-inputs/address-input.tsx | 6 + src/contract-ui/tabs/analytics/page.tsx | 4 +- .../quantity-input-with-unlimited.tsx | 2 + .../components/snapshot-upload.tsx | 2 + src/contract-ui/tabs/code/page.tsx | 4 +- .../tabs/embed/components/embed-setup.tsx | 10 +- .../tabs/events/components/events-feed.tsx | 10 +- src/contract-ui/tabs/events/page.tsx | 4 +- .../tabs/listings/components/table.tsx | 2 + .../tabs/nfts/components/airdrop-upload.tsx | 2 + .../tabs/nfts/components/table.tsx | 2 + .../tabs/overview/components/LatestEvents.tsx | 10 +- .../overview/components/PermissionsTable.tsx | 10 +- src/contract-ui/tabs/payments/page.tsx | 4 +- src/contract-ui/tabs/permissions/page.tsx | 4 +- .../shared-components/marketplace-table.tsx | 2 + src/core-ui/batch-upload/progress-box.tsx | 2 + src/core-ui/sidebar/tunnel.tsx | 4 +- src/hooks/common/useDebounce.ts | 19 -- src/hooks/effect/useParallexEffect.tsx | 2 + src/hooks/useDelayedDisplay.ts | 19 -- src/hooks/useLocalStorage.ts | 2 + src/hooks/useNativeColorMode.ts | 4 +- src/pages/[chain_id]/[...paths].tsx | 2 + src/pages/_app.tsx | 8 + src/pages/cli/login.tsx | 21 +- src/pages/contracts/deploy/[contractId].tsx | 2 + .../dashboard/connect/account-abstraction.tsx | 28 +-- src/pages/dashboard/connect/analytics.tsx | 34 ++- .../dashboard/connect/in-app-wallets.tsx | 34 ++- src/pages/dashboard/connect/index.tsx | 14 -- src/pages/dashboard/connect/pay.tsx | 57 ++--- src/pages/dashboard/connect/playground.tsx | 40 ++-- .../dashboard/settings/api-keys/[id].tsx | 25 +-- src/pages/dashboard/settings/billing.tsx | 9 +- src/pages/dashboard/settings/index.tsx | 14 -- src/pages/frame/degen/mint.tsx | 2 + src/pages/frame/mint/base/index.tsx | 2 + src/pages/profile/[profileAddress].tsx | 3 + src/tw-components/AddressCopyButton.tsx | 9 +- src/tw-components/button.tsx | 10 +- src/tw-components/code-block.tsx | 9 +- 97 files changed, 577 insertions(+), 514 deletions(-) delete mode 100644 src/hooks/common/useDebounce.ts delete mode 100644 src/hooks/useDelayedDisplay.ts delete mode 100644 src/pages/dashboard/connect/index.tsx delete mode 100644 src/pages/dashboard/settings/index.tsx diff --git a/redirects.js b/redirects.js index d50909741c..24f3bdb3d1 100644 --- a/redirects.js +++ b/redirects.js @@ -129,6 +129,16 @@ function redirects() { destination: "/dashboard/settings/api-keys", permanent: false, }, + { + source: "/dashboard/settings", + destination: "/dashboard/settings/api-keys", + permanent: false, + }, + { + source: "/dashboard/connect", + destination: "/dashboard/connect/playground", + permanent: false, + }, { source: "/dashboard/wallet", destination: "/dashboard/connect/playground", diff --git a/src/@/components/ui/ScrollShadow/ScrollShadow.tsx b/src/@/components/ui/ScrollShadow/ScrollShadow.tsx index 795f257067..08634f1754 100644 --- a/src/@/components/ui/ScrollShadow/ScrollShadow.tsx +++ b/src/@/components/ui/ScrollShadow/ScrollShadow.tsx @@ -2,7 +2,7 @@ "use client"; import { cn } from "@/lib/utils"; -import { useEffect, useRef } from "react"; +import { useLayoutEffect, useRef } from "react"; import styles from "./ScrollShadow.module.css"; export function ScrollShadow(props: { @@ -17,7 +17,7 @@ export function ScrollShadow(props: { const shadowRightEl = useRef(null); const wrapperEl = useRef(null); - useEffect(() => { + useLayoutEffect(() => { const content = scrollableEl.current; const shadowTop = shadowTopEl.current; const shadowBottom = shadowBottomEl.current; diff --git a/src/@/components/ui/tabs.tsx b/src/@/components/ui/tabs.tsx index 03b01a61bd..939fc0a203 100644 --- a/src/@/components/ui/tabs.tsx +++ b/src/@/components/ui/tabs.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, useCallback, useEffect } from "react"; +import { useRef, useState, useCallback, useLayoutEffect } from "react"; import { cn } from "../../lib/utils"; import { ScrollShadow } from "./ScrollShadow/ScrollShadow"; import { Button } from "./button"; @@ -23,7 +23,7 @@ export function TabLinks(props: { setActiveTabEl(el); }, []); - useEffect(() => { + useLayoutEffect(() => { if (activeTabEl && containerRef.current && lineRef.current) { const containerRect = containerRef.current.getBoundingClientRect(); const lineEl = lineRef.current; diff --git a/src/@3rdweb-sdk/react/hooks/useActiveChainId.tsx b/src/@3rdweb-sdk/react/hooks/useActiveChainId.tsx index 2130d04b68..cfc75432b3 100644 --- a/src/@3rdweb-sdk/react/hooks/useActiveChainId.tsx +++ b/src/@3rdweb-sdk/react/hooks/useActiveChainId.tsx @@ -48,6 +48,8 @@ export function EVMContractInfoProvider(props: { }); }, []); + // no better way right now - this whole file is going away with RSC migration + // eslint-disable-next-line no-restricted-syntax useEffect(() => { _setValue(props.value); }, [_setValue, props.value]); diff --git a/src/@3rdweb-sdk/react/hooks/useApi.ts b/src/@3rdweb-sdk/react/hooks/useApi.ts index f233696a13..237b7e9d0d 100644 --- a/src/@3rdweb-sdk/react/hooks/useApi.ts +++ b/src/@3rdweb-sdk/react/hooks/useApi.ts @@ -1085,8 +1085,9 @@ export function useApiAuthToken() { const [paymentsSellerId, setPaymentsSellerId] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - // not using a query because we don't want to store this in any cache + // not using a query because we don't want to store this in any cache + // eslint-disable-next-line no-restricted-syntax useEffect(() => { let mounted = true; setError(null); diff --git a/src/app/(dashboard)/(chain)/chainlist/components/client/search.tsx b/src/app/(dashboard)/(chain)/chainlist/components/client/search.tsx index a7c81e400b..522ba9578c 100644 --- a/src/app/(dashboard)/(chain)/chainlist/components/client/search.tsx +++ b/src/app/(dashboard)/(chain)/chainlist/components/client/search.tsx @@ -2,7 +2,7 @@ import { SearchIcon, XCircleIcon } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useRef } from "react"; +import { useRef } from "react"; import { useDebouncedCallback } from "use-debounce"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -21,16 +21,14 @@ export const SearchInput: React.FC = () => { const inputRef = useRef(null); - // sync the input state back with the searchParams on updates - useEffect(() => { - if ( - inputRef.current && - inputRef.current.value && - !searchParams?.get("query") - ) { - inputRef.current.value = ""; - } - }, [searchParams]); + // hah, no need for useEffect here this just works! + if ( + inputRef.current && + inputRef.current.value && + !searchParams?.get("query") + ) { + inputRef.current.value = ""; + } const handleSearch = useDebouncedCallback((term: string) => { const params = new URLSearchParams(searchParams ?? undefined); diff --git a/src/app/components/Header/MobileBurgerMenu.tsx b/src/app/components/Header/MobileBurgerMenu.tsx index db6fe4c3e1..8d61ae96e5 100644 --- a/src/app/components/Header/MobileBurgerMenu.tsx +++ b/src/app/components/Header/MobileBurgerMenu.tsx @@ -1,7 +1,7 @@ "use client"; import { Menu, XIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useLayoutEffect, useState } from "react"; import { Button } from "../../../@/components/ui/button"; import { headerLinks } from "./headerLinks"; import Link from "next/link"; @@ -9,7 +9,7 @@ import Link from "next/link"; export function MobileBurgerMenu() { const [showBurgerMenu, setShowBurgerMenu] = useState(false); - useEffect(() => { + useLayoutEffect(() => { if (showBurgerMenu) { document.body.style.overflow = "hidden"; } else { @@ -17,7 +17,7 @@ export function MobileBurgerMenu() { } }, [showBurgerMenu]); - useEffect(() => { + useLayoutEffect(() => { return () => { document.body.style.overflow = "auto"; }; diff --git a/src/app/components/posthog-pageview.tsx b/src/app/components/posthog-pageview.tsx index f71fd8fff8..e177b6c2e2 100644 --- a/src/app/components/posthog-pageview.tsx +++ b/src/app/components/posthog-pageview.tsx @@ -9,7 +9,9 @@ export default function PostHogPageView(): null { const pathname = usePathname(); const searchParams = useSearchParams(); const posthog = usePostHog(); + // FIXME: find a better way that does not require useEffect - this is bad, we need a better way (useEffect might fire twice etc etc) // Track pageviews + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (pathname && posthog && searchParams) { let url = window.origin + pathname; diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index d8388b8b59..a96c50ecdb 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -10,6 +10,8 @@ export default function GlobalError({ error: Error & { digest?: string }; reset: () => void; }) { + // legitimate usecase + // eslint-disable-next-line no-restricted-syntax useEffect(() => { Sentry.captureException(error); }, [error]); diff --git a/src/components/ClientOnly/ClientOnly.tsx b/src/components/ClientOnly/ClientOnly.tsx index 634239d974..667b84d3f4 100644 --- a/src/components/ClientOnly/ClientOnly.tsx +++ b/src/components/ClientOnly/ClientOnly.tsx @@ -22,6 +22,8 @@ export const ClientOnly: ComponentWithChildren = ({ }) => { const [hasMounted, setHasMounted] = useState(false); + // FIXME: legitimate usecase - however ideally we wouldn't need this entire file + // eslint-disable-next-line no-restricted-syntax useEffect(() => { setHasMounted(true); }, []); diff --git a/src/components/analytics/area-chart.tsx b/src/components/analytics/area-chart.tsx index 8cb2c9d789..87a55ef028 100644 --- a/src/components/analytics/area-chart.tsx +++ b/src/components/analytics/area-chart.tsx @@ -208,6 +208,9 @@ function generateFakeData() { export const AreaChartLoadingState: React.FC = () => { const [loadingData, setLoadingData] = useState(generateFakeData()); + + // legitimate use case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const interval = setInterval(() => { setLoadingData(generateFakeData()); diff --git a/src/components/app-layouts/app.tsx b/src/components/app-layouts/app.tsx index 5298bada34..8e8572c2bf 100644 --- a/src/components/app-layouts/app.tsx +++ b/src/components/app-layouts/app.tsx @@ -123,6 +123,8 @@ export const AppLayout: ComponentWithChildren = (props) => { }), ); + // will be deleted as part of: https://github.com/thirdweb-dev/dashboard/pull/2648 + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!isProd) { localStorage.setItem("IS_THIRDWEB_DEV", "true"); diff --git a/src/components/app-layouts/provider-setup.tsx b/src/components/app-layouts/provider-setup.tsx index 836649fe22..328d2f3e02 100644 --- a/src/components/app-layouts/provider-setup.tsx +++ b/src/components/app-layouts/provider-setup.tsx @@ -138,6 +138,8 @@ export const DashboardThirdwebProviderSetup: ComponentWithChildren< const GlobalAuthTokenProvider = () => { const { token, isLoading } = useApiAuthToken(); + // will be deleted as part of: https://github.com/thirdweb-dev/dashboard/pull/2648 + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (token && !isLoading) { (window as any)[GLOBAL_AUTH_TOKEN_KEY] = token; diff --git a/src/components/app-layouts/providers.tsx b/src/components/app-layouts/providers.tsx index 658a933519..71244a16be 100644 --- a/src/components/app-layouts/providers.tsx +++ b/src/components/app-layouts/providers.tsx @@ -70,6 +70,8 @@ const V4ToV5SignerAdapter = () => { const currentWallet = useRef(null); + // will removes as part of: https://github.com/thirdweb-dev/dashboard/pull/2648 + // eslint-disable-next-line no-restricted-syntax useEffect(() => { let active = true; diff --git a/src/components/buttons/MismatchButton.tsx b/src/components/buttons/MismatchButton.tsx index bd71751d75..4c8d87101e 100644 --- a/src/components/buttons/MismatchButton.tsx +++ b/src/components/buttons/MismatchButton.tsx @@ -318,6 +318,8 @@ const UpsellTestnetNotice: React.FC<{ const chain = useSupportedChain(connectedChainId || -1); + // FIXME: find a better way to track the "show" state of a component + // eslint-disable-next-line no-restricted-syntax useEffect(() => { trackEvent({ category: "no-funds", diff --git a/src/components/buttons/TransactionButton.tsx b/src/components/buttons/TransactionButton.tsx index 80f1e1f6f1..3b391514f3 100644 --- a/src/components/buttons/TransactionButton.tsx +++ b/src/components/buttons/TransactionButton.tsx @@ -226,6 +226,9 @@ const ExternalApprovalNotice: React.FC = ({ const chainId = useChainId() || -1; const [showHint, setShowHint] = useState(false); + + // legitimate usecase! + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const t = setTimeout(() => { setShowHint(true); diff --git a/src/components/cmd-k-search/index.tsx b/src/components/cmd-k-search/index.tsx index 2a67a4c3da..b682c0a867 100644 --- a/src/components/cmd-k-search/index.tsx +++ b/src/components/cmd-k-search/index.tsx @@ -21,7 +21,6 @@ import { useAddress } from "@thirdweb-dev/react"; import { ChainIcon } from "components/icons/ChainIcon"; import { useTrack } from "hooks/analytics/useTrack"; import { useAllChainsData } from "hooks/chains/allChains"; -import { useDebounce } from "hooks/common/useDebounce"; import { SearchMode, getSearchQuery } from "lib/search"; import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -29,6 +28,7 @@ import { FiArrowRight, FiSearch, FiX } from "react-icons/fi"; import invariant from "tiny-invariant"; import { Button, Card, Heading, Link, Text } from "tw-components"; import { shortenIfAddress } from "utils/usedapp-external"; +import { useDebounce } from "use-debounce"; type ContractSearchResult = { address: string; @@ -124,6 +124,8 @@ export const CmdKSearch: React.FC = () => { const trackEvent = useTrack(); const queryClient = useQueryClient(); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "k" && e.metaKey) { @@ -140,7 +142,7 @@ export const CmdKSearch: React.FC = () => { const walletAddress = useAddress(); // debounce 500ms - const debouncedSearchValue = useDebounce(searchValue, 500); + const [debouncedSearchValue] = useDebounce(searchValue, 500); const typesenseSearchQuery = useQuery( contractTypesenseSearchQuery( @@ -182,6 +184,8 @@ export const CmdKSearch: React.FC = () => { setActiveIndex(0); }, []); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // re-set the active index if we are fetching if (isFetching && !data.length) { @@ -189,6 +193,8 @@ export const CmdKSearch: React.FC = () => { } }, [data.length, isFetching]); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // only if the modal is open if (!open) { diff --git a/src/components/configure-networks/ConfigureNetworkForm.tsx b/src/components/configure-networks/ConfigureNetworkForm.tsx index a30e7137e3..4963c1e900 100644 --- a/src/components/configure-networks/ConfigureNetworkForm.tsx +++ b/src/components/configure-networks/ConfigureNetworkForm.tsx @@ -21,7 +21,6 @@ import { useAllChainsData } from "hooks/chains/allChains"; import { useSupportedChainsNameRecord } from "hooks/chains/configureChains"; import { useRemoveChainModification } from "hooks/chains/useModifyChain"; import { getDashboardChainRpc } from "lib/rpc"; -import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { Button, FormErrorMessage, FormLabel, Text } from "tw-components"; @@ -78,6 +77,9 @@ export const ConfigureNetworkForm: React.FC = ({ slug: editingChain?.slug || "", status: editingChain?.status === "deprecated" ? "deprecated" : "active", }, + defaultValues: { + slug: prefillSlug, + }, mode: "onChange", }); @@ -93,16 +95,6 @@ export const ConfigureNetworkForm: React.FC = ({ ? chainIdToChainRecord[editingChain.chainId] : undefined; - // setup prefills - useEffect(() => { - if (prefillSlug) { - form.setValue("slug", prefillSlug, { - shouldDirty: true, - shouldValidate: true, - }); - } - }, [prefillSlug, form]); - const { ref } = form.register("name", { required: true, validate: { diff --git a/src/components/connect/Carousel.tsx b/src/components/connect/Carousel.tsx index 740e77397f..324474167c 100644 --- a/src/components/connect/Carousel.tsx +++ b/src/components/connect/Carousel.tsx @@ -49,6 +49,8 @@ const Carousel = ({ TRACKING_CATEGORY }: { TRACKING_CATEGORY: string }) => { setSelectedShowCaseIdx((idx) => (idx === 2 ? 0 : idx + 1)); }; + // FIXME: this can likely be achieved fully via CSS + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!hoveredCard) { const timer = setInterval(increment, 3500); @@ -57,6 +59,8 @@ const Carousel = ({ TRACKING_CATEGORY }: { TRACKING_CATEGORY: string }) => { } }, [hoveredCard]); + // FIXME: this can likely be achieved fully via CSS + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (selectedShowCaseIdx > 0 && !canAnimate) { setCanAnimate(true); diff --git a/src/components/contract-components/contract-deploy-form/index.tsx b/src/components/contract-components/contract-deploy-form/index.tsx index ae7e807172..ee3d0afc2d 100644 --- a/src/components/contract-components/contract-deploy-form/index.tsx +++ b/src/components/contract-components/contract-deploy-form/index.tsx @@ -31,6 +31,8 @@ export const ContractDeployForm: React.FC = ({ : undefined, ); + // FIXME: all of this logic needs to be reworked + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // If the user has not selected a chain, and the connected chain is configured, select it if ( diff --git a/src/components/contract-components/contract-publish-form/custom-factory.tsx b/src/components/contract-components/contract-publish-form/custom-factory.tsx index 4ae6e33281..1576548670 100644 --- a/src/components/contract-components/contract-publish-form/custom-factory.tsx +++ b/src/components/contract-components/contract-publish-form/custom-factory.tsx @@ -31,6 +31,8 @@ export const CustomFactory: React.FC = ({ form.watch("customFactoryAddresses[0].key"), ); + // FIXME: all of this logic needs to be reworked + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (customFactoryAbi?.data) { setCustomFactoryAbi(customFactoryAbi.data); @@ -42,6 +44,8 @@ export const CustomFactory: React.FC = ({ control: form.control, }); + // FIXME: all of this logic needs to be reworked + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (fields.length === 0) { append({ key: 1, value: "" }, { shouldFocus: false }); diff --git a/src/components/contract-components/contract-publish-form/external-links-fieldset.tsx b/src/components/contract-components/contract-publish-form/external-links-fieldset.tsx index 51dbbe6dfd..02773f55a0 100644 --- a/src/components/contract-components/contract-publish-form/external-links-fieldset.tsx +++ b/src/components/contract-components/contract-publish-form/external-links-fieldset.tsx @@ -13,6 +13,8 @@ export const ExternalLinksFieldset = () => { control: form.control, }); + // FIXME: all of this logic needs to be reworked + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (fields.length === 0) { append( diff --git a/src/components/contract-components/contract-publish-form/index.tsx b/src/components/contract-components/contract-publish-form/index.tsx index 1a00be70b3..6cf6ebeb56 100644 --- a/src/components/contract-components/contract-publish-form/index.tsx +++ b/src/components/contract-components/contract-publish-form/index.tsx @@ -26,7 +26,7 @@ import { import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useRouter } from "next/router"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { IoChevronBack } from "react-icons/io5"; import { Button, Text } from "tw-components"; @@ -167,6 +167,8 @@ export const ContractPublishForm: React.FC = ({ }); const hasTrackedImpression = useRef(false); + // TODO: figure out a better way to track impressions + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (publishMetadata.data && !hasTrackedImpression.current) { hasTrackedImpression.current = true; @@ -266,7 +268,7 @@ export const ContractPublishForm: React.FC = ({ // during loading and after success we should stay in loading state const isLoading = publishMutation.isLoading || publishMutation.isSuccess; - useEffect(() => { + useLayoutEffect(() => { window?.scrollTo({ top: 0, behavior: "smooth", diff --git a/src/components/contract-components/tables/deployed-contracts.tsx b/src/components/contract-components/tables/deployed-contracts.tsx index 4ff6d0bec3..7e0156b749 100644 --- a/src/components/contract-components/tables/deployed-contracts.tsx +++ b/src/components/contract-components/tables/deployed-contracts.tsx @@ -396,6 +396,9 @@ const ContractTable: ComponentWithChildren = ({ // the ShowMoreButton component callback sets this state variable const [numRowsOnPage, setNumRowsOnPage] = useState(limit); // when the state variable is updated, update the page size + + // FIXME: re-work tables and pagination with @tanstack/table@latest - which (I believe) does not need this workaround anymore + // eslint-disable-next-line no-restricted-syntax useEffect(() => { setPageSize(numRowsOnPage); }, [numRowsOnPage, pageSize, setPageSize]); diff --git a/src/components/contract-functions/interactive-abi-function.tsx b/src/components/contract-functions/interactive-abi-function.tsx index 5833b1971c..fc9be447b6 100644 --- a/src/components/contract-functions/interactive-abi-function.tsx +++ b/src/components/contract-functions/interactive-abi-function.tsx @@ -171,6 +171,8 @@ export const InteractiveAbiFunction: React.FC = ({ const connectedWalletAddress = useAddress() || constants.AddressZero; + // legitimate(?) use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if ( form.watch("params").length === 0 && diff --git a/src/components/contract-pages/forms/properties.shared.tsx b/src/components/contract-pages/forms/properties.shared.tsx index fd61a38f7c..19a1807588 100644 --- a/src/components/contract-pages/forms/properties.shared.tsx +++ b/src/components/contract-pages/forms/properties.shared.tsx @@ -57,6 +57,8 @@ export const PropertiesFormControl = < name: "attributes" as ArrayPath, }); + // TODO: do we need this? + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (fields.length === 0) { append({ trait_type: "", value: "" } as any, { shouldFocus: false }); diff --git a/src/components/custom-contract/contract-header/contract-metadata.tsx b/src/components/custom-contract/contract-header/contract-metadata.tsx index 36080c7459..fe35559dd1 100644 --- a/src/components/custom-contract/contract-header/contract-metadata.tsx +++ b/src/components/custom-contract/contract-header/contract-metadata.tsx @@ -24,6 +24,8 @@ export const ContractMetadata: React.FC = ({ ); const latestPublished = publishedContractsFromDeploy.data?.slice(-1)[0]; + // legitimate, we use this to keep the state around and *only* flip it if the status changes explicitly from error to success etc + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (contractMetadataQuery.isError) { setWasError(true); diff --git a/src/components/delayed-display/delayed-display.tsx b/src/components/delayed-display/delayed-display.tsx index 38e1fc3f65..2469c08d98 100644 --- a/src/components/delayed-display/delayed-display.tsx +++ b/src/components/delayed-display/delayed-display.tsx @@ -1,10 +1,28 @@ -import useDelayedDisplay from "hooks/useDelayedDisplay"; +import { useState, useEffect } from "react"; import { ComponentWithChildren } from "types/component-with-children"; interface DelayedDisplayProps { delay: number; } +function useDelayedDisplay(delay: number) { + const [displayContent, setDisplayContent] = useState(false); + + // FIXME: this is a weird thing, we should not need it - in the meantime this is a legit use case + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + const timer = setTimeout(() => { + setDisplayContent(true); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [delay]); + + return displayContent; +} + export const DelayedDisplay: ComponentWithChildren = ({ delay, children, diff --git a/src/components/embedded-wallets/Configure/index.tsx b/src/components/embedded-wallets/Configure/index.tsx index ff593bf1fd..1b0826cf27 100644 --- a/src/components/embedded-wallets/Configure/index.tsx +++ b/src/components/embedded-wallets/Configure/index.tsx @@ -88,6 +88,9 @@ export const Configure: React.FC = ({ control: form.control, name: "customAuthEndpoint.customHeaders", }); + + // FIXME: jesus do we need this? - there has to be a better way + // eslint-disable-next-line no-restricted-syntax useEffect(() => { form.reset({ customAuthEndpoint: config.customAuthEndpoint, diff --git a/src/components/engine/contract-subscription/contract-subscriptions-table.tsx b/src/components/engine/contract-subscription/contract-subscriptions-table.tsx index 9866ab0025..612283d724 100644 --- a/src/components/engine/contract-subscription/contract-subscriptions-table.tsx +++ b/src/components/engine/contract-subscription/contract-subscriptions-table.tsx @@ -18,6 +18,7 @@ import { UseDisclosureReturn, useDisclosure, } from "@chakra-ui/react"; +import { useQuery } from "@tanstack/react-query"; import { createColumnHelper } from "@tanstack/react-table"; import { shortenAddress } from "@thirdweb-dev/react"; import { ChainIcon } from "components/icons/ChainIcon"; @@ -27,7 +28,7 @@ import { useTrack } from "hooks/analytics/useTrack"; import { useAllChainsData } from "hooks/chains/allChains"; import { useTxNotifications } from "hooks/useTxNotifications"; import { thirdwebClient } from "lib/thirdweb-client"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { FiInfo, FiTrash } from "react-icons/fi"; import { defineChain, eth_getBlockByNumber, getRpcClient } from "thirdweb"; import { Button, Card, FormLabel, LinkButton, Text } from "tw-components"; @@ -150,36 +151,31 @@ const ChainLastBlockTimestamp = ({ chainId: number; blockNumber: bigint; }) => { - const rpcRequest = getRpcClient({ - client: thirdwebClient, - chain: defineChain(chainId), - }); - const [lastBlockTimestamp, setLastBlockTimestamp] = useState( - null, - ); - // Get the block timestamp to display how delayed the last processed block is. - useEffect(() => { - (async () => { - try { - const block = await eth_getBlockByNumber(rpcRequest, { - blockNumber, - includeTransactions: false, - }); - setLastBlockTimestamp(new Date(Number(block.timestamp) * 1000)); - } catch (e) { - console.error("Failed to get block details:", e); - } - })(); - }, [rpcRequest, blockNumber]); + const ethBlockQuery = useQuery({ + queryKey: ["block_timestamp", chainId, blockNumber], + // keep the previous data while fetching new data + keepPreviousData: true, + queryFn: async () => { + const rpcRequest = getRpcClient({ + client: thirdwebClient, + chain: defineChain(chainId), + }); + const block = await eth_getBlockByNumber(rpcRequest, { + blockNumber, + includeTransactions: false, + }); + return new Date(Number(block.timestamp) * 1000); + }, + }); - if (!lastBlockTimestamp) { + if (!ethBlockQuery.data) { return null; } return ( - {format(lastBlockTimestamp, "PP pp z")} + {format(ethBlockQuery.data, "PP pp z")} ); }; diff --git a/src/components/engine/overview/backend-wallets-table.tsx b/src/components/engine/overview/backend-wallets-table.tsx index 73fd7f786b..d151b65f51 100644 --- a/src/components/engine/overview/backend-wallets-table.tsx +++ b/src/components/engine/overview/backend-wallets-table.tsx @@ -31,7 +31,7 @@ import { TWTable } from "components/shared/TWTable"; import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import QRCode from "qrcode"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; import { BiExport, BiImport, BiPencil } from "react-icons/bi"; import { @@ -45,6 +45,7 @@ import { import { AddressCopyButton } from "tw-components/AddressCopyButton"; import { prettyPrintCurrency } from "../utils"; import { utils } from "ethers"; +import { useQuery } from "@tanstack/react-query"; interface BackendWalletsTableProps { wallets: BackendWallet[]; @@ -326,14 +327,27 @@ const ReceiveFundsModal = ({ backendWallet: BackendWallet; disclosure: UseDisclosureReturn; }) => { - const [dataBase64, setDataBase64] = useState(""); - - useEffect(() => { - QRCode.toDataURL(backendWallet.address, (error: any, dataUrl: string) => { - console.error("error", error); - setDataBase64(dataUrl); - }); - }, [backendWallet]); + const qrCodeBase64Query = useQuery({ + queryKey: ["engine", "receive-funds-qr-code", backendWallet.address], + // only run this if we have a backendWallet address + enabled: !!backendWallet.address, + // start out with empty string + initialData: "", + queryFn: async () => { + return new Promise((resolve, reject) => { + QRCode.toDataURL( + backendWallet.address, + (error: any, dataUrl: string) => { + if (error) { + reject(error); + } else { + resolve(dataUrl); + } + }, + ); + }); + }, + }); return ( @@ -352,7 +366,7 @@ const ReceiveFundsModal = ({ size="sm" /> Receive funds to your backend wallet { - const calculateTimeLeft = () => { - const difference = Number(new Date(dateStr)) - Number(new Date()); - let timeLeft = { - days: 0, - hours: 0, - minutes: 0, - seconds: 0, - }; +function calculateTimeLeft(dateStr: string) { + const difference = Number(new Date(dateStr)) - Number(new Date()); + let timeLeft = { + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + }; - if (difference > 0) { - timeLeft = { - days: Math.floor(difference / (1000 * 60 * 60 * 24)), - hours: Math.floor((difference / (1000 * 60 * 60)) % 24), - minutes: Math.floor((difference / 1000 / 60) % 60), - seconds: Math.floor((difference / 1000) % 60), - }; - } + if (difference > 0) { + timeLeft = { + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + }; + } - return timeLeft as ITimeLeft; - }; + return timeLeft as ITimeLeft; +} - const [timeLeft, setTimeLeft] = useState(calculateTimeLeft()); +const Timer = ({ dateStr }: TimerProps) => { + const [timeLeft, setTimeLeft] = useState( + calculateTimeLeft(dateStr), + ); const { days, hours, minutes, seconds } = timeLeft; + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const interval = setInterval(() => { - setTimeLeft(calculateTimeLeft()); + setTimeLeft(calculateTimeLeft(dateStr)); }, 1000); return () => clearInterval(interval); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [dateStr]); const items = [ { label: "Day", value: days }, diff --git a/src/components/hackathon/common/Timer.tsx b/src/components/hackathon/common/Timer.tsx index dfbb8e0670..553cf565fd 100644 --- a/src/components/hackathon/common/Timer.tsx +++ b/src/components/hackathon/common/Timer.tsx @@ -13,31 +13,35 @@ interface TimerProps { date: string; } -const Timer: React.FC = ({ date }) => { - const calculateTimeLeft = () => { - const difference = Number(new Date(date)) - Number(new Date()); - let timeLeft = {}; +function calculateTimeLeft(date: string) { + const difference = Number(new Date(date)) - Number(new Date()); + let timeLeft = {}; - if (difference > 0) { - timeLeft = { - days: Math.floor(difference / (1000 * 60 * 60 * 24)), - hours: Math.floor((difference / (1000 * 60 * 60)) % 24), - minutes: Math.floor((difference / 1000 / 60) % 60), - seconds: Math.floor((difference / 1000) % 60), - }; - } + if (difference > 0) { + timeLeft = { + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + }; + } - return timeLeft as ITimeLeft; - }; + return timeLeft as ITimeLeft; +} - const [timeLeft, setTimeLeft] = useState(calculateTimeLeft()); +const Timer: React.FC = ({ date }) => { + const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(date)); const { days, hours, minutes, seconds } = timeLeft; + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { - setTimeout(() => { - setTimeLeft(calculateTimeLeft()); + const interval = setInterval(() => { + setTimeLeft(calculateTimeLeft(date)); }, 1000); - }); + + return () => clearInterval(interval); + }, [date]); const items = [ { label: "Day", value: days }, diff --git a/src/components/help/contact-support-modal.tsx b/src/components/help/contact-support-modal.tsx index 7c3e4c97e4..18367811c6 100644 --- a/src/components/help/contact-support-modal.tsx +++ b/src/components/help/contact-support-modal.tsx @@ -75,6 +75,8 @@ export const ContactSupportModal = () => { const { mutate: createTicket } = useCreateTicket(); // On changing product -> reset all fields (but keep the `product` field) + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!productLabel) { return; diff --git a/src/components/homepage/CodeBlock/index.tsx b/src/components/homepage/CodeBlock/index.tsx index 5f15693eb2..5b59ae9604 100644 --- a/src/components/homepage/CodeBlock/index.tsx +++ b/src/components/homepage/CodeBlock/index.tsx @@ -67,7 +67,7 @@ export const HomePageCodeBlock: React.FC = ({ lightTheme || lightThemeDefault, darkTheme || darkThemeDefault, ); - const { onCopy, hasCopied, setValue } = useClipboard(code); + const { onCopy, hasCopied } = useClipboard(code); const [speedUpEnabled, setSpeedUpEnabled] = useState(false); const [currentCodeIndex, setCurrentCodeIndex] = useState(0); const [currentTypingSpeed] = useState(typingSpeed); @@ -75,6 +75,8 @@ export const HomePageCodeBlock: React.FC = ({ const chakraTheme = useTheme(); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!inView) { return; @@ -91,18 +93,14 @@ export const HomePageCodeBlock: React.FC = ({ return () => clearInterval(interval); }, [currentCodeIndex, currentTypingSpeed, code, inView]); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (containerRef.current) { containerRef.current.scrollTop = containerRef.current.scrollHeight; } }, [currentCodeIndex]); - useEffect(() => { - if (code) { - setValue(code); - } - }, [code, setValue]); - return ( = ({ const trackEvent = useTrack(); // We close the modal when the user is on the Growth plan successfully + // FIXME: this needs to be re-worked + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (account.data?.plan === AccountPlan.Growth) { onClose(); diff --git a/src/components/inputs/BasisPointsInput.tsx b/src/components/inputs/BasisPointsInput.tsx index a330ad6593..597661b7f5 100644 --- a/src/components/inputs/BasisPointsInput.tsx +++ b/src/components/inputs/BasisPointsInput.tsx @@ -26,12 +26,14 @@ export const BasisPointsInput: React.FC = ({ // updated on the settings tab, when the value gets // changed from the default 0, but only then, and not // every time the value changes on user input + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (value !== 0 && stringValue === "0.00") { setStringValue((value / 100).toFixed(2)); } }, [value, stringValue]); + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const validValue = stringValue.match( /^100$|^100.00$|^\d{0,2}(\.\d{1,2})? *%?$/g, diff --git a/src/components/landing-pages/image-section-item.tsx b/src/components/landing-pages/image-section-item.tsx index d0c22cda62..7993fabbbc 100644 --- a/src/components/landing-pages/image-section-item.tsx +++ b/src/components/landing-pages/image-section-item.tsx @@ -1,6 +1,5 @@ import { Flex, FlexProps } from "@chakra-ui/react"; import { StaticImageData } from "next/image"; -import React from "react"; import { Text } from "tw-components"; import { LandingDesktopMobileImage } from "./desktop-mobile-image"; diff --git a/src/components/mission/HowSection.tsx b/src/components/mission/HowSection.tsx index fdc2fef772..3d7870c8ac 100644 --- a/src/components/mission/HowSection.tsx +++ b/src/components/mission/HowSection.tsx @@ -51,27 +51,29 @@ const HowSection = ({ TRACKING_CATEGORY }: HowSectionProps) => { const [offsetY, setOffsetY] = useState(0); const ref = useRef(null); - const handleScroll = () => { - if (ref.current) { - const elementTop = - ref.current.getBoundingClientRect().top + window.pageYOffset; + // somewhat legitimate use-case + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + function handleScroll() { + if (ref.current) { + const elementTop = + ref.current.getBoundingClientRect().top + window.pageYOffset; - const startOffset = window.innerHeight / 2; - const scrollPosition = window.pageYOffset; + const startOffset = window.innerHeight / 2; + const scrollPosition = window.pageYOffset; - if (scrollPosition > elementTop - startOffset) { - const newOffset = Math.min( - (scrollPosition - elementTop + startOffset) * 0.2, - 150, - ); - setOffsetY(newOffset); - } else { - setOffsetY(0); + if (scrollPosition > elementTop - startOffset) { + const newOffset = Math.min( + (scrollPosition - elementTop + startOffset) * 0.2, + 150, + ); + setOffsetY(newOffset); + } else { + setOffsetY(0); + } } } - }; - useEffect(() => { window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); diff --git a/src/components/onboarding/ApplyForOpCreditsForm.tsx b/src/components/onboarding/ApplyForOpCreditsForm.tsx index 44ff174ea2..d455da04d4 100644 --- a/src/components/onboarding/ApplyForOpCreditsForm.tsx +++ b/src/components/onboarding/ApplyForOpCreditsForm.tsx @@ -75,6 +75,8 @@ export const ApplyForOpCreditsForm: React.FC = ({ "Something went wrong, please try again.", ); + // TODO: find better way to track impressions + // eslint-disable-next-line no-restricted-syntax useEffect(() => { trackEvent({ category: "op-sponsorship", diff --git a/src/components/onboarding/ApplyForOpCreditsModal.tsx b/src/components/onboarding/ApplyForOpCreditsModal.tsx index cbe58cb961..c5791b4f59 100644 --- a/src/components/onboarding/ApplyForOpCreditsModal.tsx +++ b/src/components/onboarding/ApplyForOpCreditsModal.tsx @@ -103,6 +103,8 @@ export const ApplyForOpCreditsModal: React.FC = ({ ); const trackEvent = useTrack(); + // TODO: find better way to track impressions + // eslint-disable-next-line no-restricted-syntax useEffect(() => { trackEvent({ category: "op-sponsorship", diff --git a/src/components/onboarding/Steps.tsx b/src/components/onboarding/Steps.tsx index 3e4e10bd88..d7fc2b9c5e 100644 --- a/src/components/onboarding/Steps.tsx +++ b/src/components/onboarding/Steps.tsx @@ -178,6 +178,8 @@ export const OnboardingSteps: React.FC = ({ }); }; + // TODO: find better way to track impressions + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (currentStep) { trackEvent({ diff --git a/src/components/onboarding/index.tsx b/src/components/onboarding/index.tsx index f2157e5b39..62edff6323 100644 --- a/src/components/onboarding/index.tsx +++ b/src/components/onboarding/index.tsx @@ -135,6 +135,8 @@ export const Onboarding: React.FC = () => { } }; + // FIXME: this entire flow needs reworked - re-vist as part of FTUX imrpovements + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!isLoggedIn || meQuery.isLoading) { return; @@ -152,6 +154,8 @@ export const Onboarding: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoggedIn, meQuery]); + // FIXME: this entire flow needs reworked - re-vist as part of FTUX imrpovements + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!account || state || !wallet) { return; diff --git a/src/components/product-pages/common/nav/HoverMenu.tsx b/src/components/product-pages/common/nav/HoverMenu.tsx index 307caa922a..b6abf706f6 100644 --- a/src/components/product-pages/common/nav/HoverMenu.tsx +++ b/src/components/product-pages/common/nav/HoverMenu.tsx @@ -29,6 +29,8 @@ export const HoverMenu: React.FC = ({ const triggerRef = useRef(null); const width = columns === 2 ? 660 : 336; + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const updateOffset = () => { const el = triggerRef.current; diff --git a/src/components/product-pages/common/nav/NestedHoverMenu.tsx b/src/components/product-pages/common/nav/NestedHoverMenu.tsx index f102fdc169..9ef797171d 100644 --- a/src/components/product-pages/common/nav/NestedHoverMenu.tsx +++ b/src/components/product-pages/common/nav/NestedHoverMenu.tsx @@ -63,6 +63,8 @@ export const NestedHoverMenu: React.FC = ({ }, 100); }; + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const updateOffset = () => { const el = triggerRef.current; diff --git a/src/components/selects/NetworkSelectorButton.tsx b/src/components/selects/NetworkSelectorButton.tsx index 2a597fd0b9..640aebd7f1 100644 --- a/src/components/selects/NetworkSelectorButton.tsx +++ b/src/components/selects/NetworkSelectorButton.tsx @@ -74,6 +74,8 @@ export const NetworkSelectorButton: React.FC = ({ const prevChain = useRef(chain); // handle switch network done from wallet app/extension + // TODO: legitimate use-case, but maybe theres a better way to hook into this? + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!chain) { return; diff --git a/src/components/settings/Account/Billing/ConnectWallet.tsx b/src/components/settings/Account/Billing/ConnectWallet.tsx index 622cb5c3d8..ceeea3e940 100644 --- a/src/components/settings/Account/Billing/ConnectWallet.tsx +++ b/src/components/settings/Account/Billing/ConnectWallet.tsx @@ -13,6 +13,8 @@ export const BillingConnectWalletPrompt = () => { const setIsWalletModalOpen = useSetIsWalletModalOpen(); const setModalConfig = useSetWalletModalConfig(); + // will be removed as part of: https://github.com/thirdweb-dev/dashboard/pull/2648 - we then can just make a specific button with its own props for it + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // trigger button if (!modalOpened.current) { diff --git a/src/components/settings/Account/Billing/index.tsx b/src/components/settings/Account/Billing/index.tsx index 96f3c129bb..f7a26412ef 100644 --- a/src/components/settings/Account/Billing/index.tsx +++ b/src/components/settings/Account/Billing/index.tsx @@ -188,6 +188,8 @@ export const Billing: React.FC = ({ account }) => { ]; }, [account, onPaymentMethodOpen, paymentMethodSaving, stepsCompleted]); + // FIXME: this entire flow needs to be re-worked + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (account) { const paymentCompleted = validPayment; diff --git a/src/components/share-buttom/index.tsx b/src/components/share-buttom/index.tsx index e155809fb2..71e10e9f5b 100644 --- a/src/components/share-buttom/index.tsx +++ b/src/components/share-buttom/index.tsx @@ -1,18 +1,11 @@ import { Icon, useClipboard, useToast } from "@chakra-ui/react"; -import { useEffect } from "react"; import { FiCheck, FiShare2 } from "react-icons/fi"; import { TrackedIconButton } from "tw-components"; export const ShareButton: React.FC>> = ( shareData, ) => { - const { onCopy, hasCopied, setValue } = useClipboard(shareData.url); - - useEffect(() => { - if (shareData.url) { - setValue(shareData.url); - } - }, [shareData.url, setValue]); + const { onCopy, hasCopied } = useClipboard(shareData.url); const toast = useToast(); const onShareClick = async () => { diff --git a/src/components/shared/BigNumberInput.tsx b/src/components/shared/BigNumberInput.tsx index 102cecf12b..37089dd88a 100644 --- a/src/components/shared/BigNumberInput.tsx +++ b/src/components/shared/BigNumberInput.tsx @@ -33,7 +33,10 @@ export const BigNumberInput: React.FC = ({ const _max = useMemo(() => { return utils.formatUnits(max, decimals).toString(); }, [decimals, max]); + // update current value + // FIXME: there must be a better way... + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (value === undefined || value === null || value === "") { setInputvalue(""); diff --git a/src/components/shared/ProgressBar.tsx b/src/components/shared/ProgressBar.tsx index 7b2036fdbe..1f3b2bd131 100644 --- a/src/components/shared/ProgressBar.tsx +++ b/src/components/shared/ProgressBar.tsx @@ -36,6 +36,10 @@ export const ProgressBar: React.FC = (props) => { opacity: isVisible ? 1 : 0, zIndex: 9999999999, } as React.CSSProperties; + + // somewhat legitimate use-case + // TODO: do we really need this? + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // Declare timeout let status: "in-progress" | "idle" = "idle"; diff --git a/src/components/wallets/ConnectWalletPlayground/Playground.tsx b/src/components/wallets/ConnectWalletPlayground/Playground.tsx index bd70ace3b6..d788be4cee 100644 --- a/src/components/wallets/ConnectWalletPlayground/Playground.tsx +++ b/src/components/wallets/ConnectWalletPlayground/Playground.tsx @@ -55,6 +55,7 @@ import { usePlaygroundWallets } from "./usePlaygroundWallets"; import { usePlaygroundTheme } from "./usePlaygroundTheme"; import { useTrack } from "hooks/analytics/useTrack"; import { FiChevronRight } from "react-icons/fi"; +import { useQuery } from "@tanstack/react-query"; type LocaleId = "en-US" | "ja-JP" | "es-ES"; @@ -96,6 +97,8 @@ export const ConnectWalletPlayground: React.FC<{ undefined, ); + // FIXME: instead of states we should use a form and then that can handle this + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (welcomeScreen) { if ( @@ -111,7 +114,6 @@ export const ConnectWalletPlayground: React.FC<{ const { colorMode, toggleColorMode } = useColorMode(); const selectedTheme = colorMode === "light" ? "light" : "dark"; const [locale, setLocale] = useState("en-US"); - const [code, setCode] = useState(""); const { colorOverrides, themeObj, setColorOverrides } = usePlaygroundTheme(selectedTheme); @@ -136,110 +138,119 @@ export const ConnectWalletPlayground: React.FC<{ Phantom: false, }); - useEffect(() => { - const getSupportedWalletsCode = ( - walletIds: WalletId[], - ): string | undefined => { - if (!walletIds.length) { - return undefined; - } + const codeQuery = useQuery({ + queryKey: [ + "playground-code", + { + btnTitle, + enabledWallets, + modalTitle, + selectedTheme, + modalSize, + smartWalletOptions, + modalTitleIconUrl, + welcomeScreen: !!welcomeScreen, + colorOverrides, + tosUrl, + privacyPolicyUrl, + locale, + socialOptions, + showThirdwebBranding, + }, + ], + queryFn: async () => { + const getSupportedWalletsCode = ( + walletIds: WalletId[], + ): string | undefined => { + if (!walletIds.length) { + return undefined; + } - return `[${walletIds - .map((walletId) => { - let walletCode = walletInfoRecord[walletId].code; - - if (walletId === "Email Wallet") { - if (!socialOptions.length) { - walletCode = "inAppWallet()"; - } else { - const options: Record = {}; - if (socialOptions.length) { - options.auth = { - options: socialOptions, - }; - } + return `[${walletIds + .map((walletId) => { + let walletCode = walletInfoRecord[walletId].code; + + if (walletId === "Email Wallet") { + if (!socialOptions.length) { + walletCode = "inAppWallet()"; + } else { + const options: Record = {}; + if (socialOptions.length) { + options.auth = { + options: socialOptions, + }; + } - walletCode = `inAppWallet(${JSON.stringify(options, null, 2)})`; + walletCode = `inAppWallet(${JSON.stringify(options, null, 2)})`; + } } - } - return walletCode; - }) - .join(",")}]`; - }; - - const _code = getCode({ - baseTheme: selectedTheme, - - colorOverrides, - imports: enabledWallets.map( - (walletId) => walletInfoRecord[walletId].import, - ), - wallets: getSupportedWalletsCode(enabledWallets), - smartWalletOptions: smartWalletOptions.enabled - ? { - gasless: smartWalletOptions.gasless, - } - : undefined, - connectWallet: { - locale: locale !== "en-US" ? `"${locale}"` : undefined, - theme: `"${selectedTheme}"`, - connectButton: btnTitle ? `{ label: "${btnTitle}" }` : undefined, - connectModal: JSON.stringify({ - size: modalSize, - title: modalTitle ? modalTitle : undefined, - titleIcon: modalTitleIconUrl.enabled - ? modalTitleIconUrl.url - : undefined, - welcomeScreen: welcomeScreen - ? Object.keys(welcomeScreen).length > 0 - ? welcomeScreen - : undefined - : undefined, - termsOfServiceUrl: tosUrl.enabled ? tosUrl.url : undefined, - privacyPolicyUrl: privacyPolicyUrl.enabled - ? privacyPolicyUrl.url - : undefined, - showThirdwebBranding: - showThirdwebBranding === false ? false : undefined, - }), - chain: undefined, - }, - }); + return walletCode; + }) + .join(",")}]`; + }; + + const _code = getCode({ + baseTheme: selectedTheme, + + colorOverrides, + imports: enabledWallets.map( + (walletId) => walletInfoRecord[walletId].import, + ), + wallets: getSupportedWalletsCode(enabledWallets), + smartWalletOptions: smartWalletOptions.enabled + ? { + gasless: smartWalletOptions.gasless, + } + : undefined, + connectWallet: { + locale: locale !== "en-US" ? `"${locale}"` : undefined, + theme: `"${selectedTheme}"`, + connectButton: btnTitle ? `{ label: "${btnTitle}" }` : undefined, + connectModal: JSON.stringify({ + size: modalSize, + title: modalTitle ? modalTitle : undefined, + titleIcon: modalTitleIconUrl.enabled + ? modalTitleIconUrl.url + : undefined, + welcomeScreen: welcomeScreen + ? Object.keys(welcomeScreen).length > 0 + ? welcomeScreen + : undefined + : undefined, + termsOfServiceUrl: tosUrl.enabled ? tosUrl.url : undefined, + privacyPolicyUrl: privacyPolicyUrl.enabled + ? privacyPolicyUrl.url + : undefined, + showThirdwebBranding: + showThirdwebBranding === false ? false : undefined, + }), + chain: undefined, + }, + }); + + async function formatCodeAndSetState( + unformattedCode: string, + ): Promise { + try { + const formattedCode = await format(unformattedCode, { + parser: "babel", + plugins: [parserBabel, estree], + printWidth: 50, + }); - async function formatCodeAndSetState( - unformattedCode: string, - ): Promise { - try { - const formattedCode = await format(unformattedCode, { - parser: "babel", - plugins: [parserBabel, estree], - printWidth: 50, - }); - setCode(formattedCode); - return formattedCode; - } catch (error) { - throw new Error(`Error formatting the code: ${error}`); + return formattedCode; + } catch (error) { + throw new Error(`Error formatting the code: ${error}`); + } } - } - formatCodeAndSetState(_code); - }, [ - btnTitle, - enabledWallets, - modalTitle, - selectedTheme, - modalSize, - smartWalletOptions, - modalTitleIconUrl, - welcomeScreen, - colorOverrides, - tosUrl, - privacyPolicyUrl, - locale, - socialOptions, - showThirdwebBranding, - ]); + return formatCodeAndSetState(_code); + }, + keepPreviousData: true, + }); + + const code = codeQuery.data || ""; const welcomeScreenContent = ( diff --git a/src/components/wallets/PosthogIdentifier.tsx b/src/components/wallets/PosthogIdentifier.tsx index 8fc8de2352..0284ae496c 100644 --- a/src/components/wallets/PosthogIdentifier.tsx +++ b/src/components/wallets/PosthogIdentifier.tsx @@ -22,6 +22,8 @@ export const PosthogIdentifier: React.FC = () => { const balance = useBalance(); const wallet = useWallet(); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (wallet) { const connector = walletIdToPHName[wallet.walletId] || wallet.walletId; @@ -30,12 +32,16 @@ export const PosthogIdentifier: React.FC = () => { } }, [wallet]); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (address) { posthog.identify(address); } }, [address]); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (chainId) { posthog.unregister("network"); @@ -43,6 +49,8 @@ export const PosthogIdentifier: React.FC = () => { } }, [chainId]); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (balance?.data?.displayValue) { posthog.register({ balance: balance.data.displayValue }); diff --git a/src/contexts/configured-chains.tsx b/src/contexts/configured-chains.tsx index 3c0668716a..d61f48a86a 100644 --- a/src/contexts/configured-chains.tsx +++ b/src/contexts/configured-chains.tsx @@ -129,6 +129,8 @@ export function ChainsProvider(props: { children: React.ReactNode }) { }, []); // save recently used chains to storage + // FIXME: probably want to move this to backend (similar to favorites) + // eslint-disable-next-line no-restricted-syntax useEffect(() => { try { localStorage.setItem( @@ -148,6 +150,8 @@ export function ChainsProvider(props: { children: React.ReactNode }) { useAllChainsData(); // get recently used chains from stroage + // FIXME: probably want to move this to backend (similar to favorites) + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!isSupportedChainsReady) { return; @@ -203,6 +207,8 @@ export function ChainsProvider(props: { children: React.ReactNode }) { ); // create supported chains and modified chains on mount + // FIXME: this should be computed not via setState + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (allChains.length === 0) { return; diff --git a/src/contexts/error-handler.tsx b/src/contexts/error-handler.tsx index 0921e408e9..234b6c6213 100644 --- a/src/contexts/error-handler.tsx +++ b/src/contexts/error-handler.tsx @@ -7,13 +7,7 @@ import { useToast, } from "@chakra-ui/react"; import type { TransactionError } from "@thirdweb-dev/sdk"; -import { - createContext, - useCallback, - useContext, - useEffect, - useState, -} from "react"; +import { createContext, useCallback, useContext, useState } from "react"; import { FiAlertTriangle, FiCheck, FiCopy, FiHelpCircle } from "react-icons/fi"; import { Button, Drawer, Heading, LinkButton, Text } from "tw-components"; import { AddressCopyButton } from "tw-components/AddressCopyButton"; @@ -56,15 +50,7 @@ export const ErrorProvider: ComponentWithChildren = ({ children }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const { onCopy, hasCopied, setValue } = useClipboard( - currentError?.message || "", - ); - - useEffect(() => { - if (currentError?.message) { - setValue(currentError?.message); - } - }, [currentError?.message, setValue]); + const { onCopy, hasCopied } = useClipboard(currentError?.message || ""); return ( <> diff --git a/src/contract-ui/components/solidity-inputs/address-input.tsx b/src/contract-ui/components/solidity-inputs/address-input.tsx index 5a07b2f653..e4fa3446c8 100644 --- a/src/contract-ui/components/solidity-inputs/address-input.tsx +++ b/src/contract-ui/components/solidity-inputs/address-input.tsx @@ -42,6 +42,8 @@ export const SolidityAddressInput: React.FC = ({ } }; + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (ensQuery.isError) { form.setError(inputName, { @@ -51,6 +53,8 @@ export const SolidityAddressInput: React.FC = ({ } }, [ensQuery.isError, form, inputName]); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (ensQuery?.data?.address && ensQuery?.data?.address !== inputNameWatch) { setValue(inputName, ensQuery.data.address, { @@ -86,6 +90,8 @@ export const SolidityAddressInput: React.FC = ({ [ensQuery?.data?.ensName, hasError, localInput], ); + // legitimate use-case (but can probably be done in form) + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // Check if the default value has changed and update localInput if (inputNameWatch !== localInput && inputNameWatch === address) { diff --git a/src/contract-ui/tabs/analytics/page.tsx b/src/contract-ui/tabs/analytics/page.tsx index 0c6bd25cec..b9de6d7aa4 100644 --- a/src/contract-ui/tabs/analytics/page.tsx +++ b/src/contract-ui/tabs/analytics/page.tsx @@ -33,7 +33,7 @@ import { useTransactionAnalytics, useUniqueWalletsAnalytics, } from "data/analytics/hooks"; -import { Suspense, useEffect, useMemo, useState } from "react"; +import { Suspense, useLayoutEffect, useMemo, useState } from "react"; import { Card, Heading, Text } from "tw-components"; interface ContractAnalyticsPageProps { @@ -52,7 +52,7 @@ export const ContractAnalyticsPage: React.FC = ({ ); const [endDate] = useState(new Date()); - useEffect(() => { + useLayoutEffect(() => { window?.scrollTo({ top: 0, behavior: "smooth" }); }, []); diff --git a/src/contract-ui/tabs/claim-conditions/components/quantity-input-with-unlimited.tsx b/src/contract-ui/tabs/claim-conditions/components/quantity-input-with-unlimited.tsx index 4895b3049d..a65c05bd16 100644 --- a/src/contract-ui/tabs/claim-conditions/components/quantity-input-with-unlimited.tsx +++ b/src/contract-ui/tabs/claim-conditions/components/quantity-input-with-unlimited.tsx @@ -30,6 +30,8 @@ export const QuantityInputWithUnlimited: React.FC< isNaN(Number(value)) ? "0" : value.toString(), ); + // FIXME: this needs a re-work + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (value !== undefined) { setStringValue(value.toString()); diff --git a/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx b/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx index db9cdb464e..d4d1ff321b 100644 --- a/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx +++ b/src/contract-ui/tabs/claim-conditions/components/snapshot-upload.tsx @@ -126,6 +126,8 @@ export const SnapshotUpload: React.FC = ({ [], ); + // FIXME: this can be a mutation or query insead! + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (validSnapshot.length === 0) { return setSnapshotData([]); diff --git a/src/contract-ui/tabs/code/page.tsx b/src/contract-ui/tabs/code/page.tsx index c47df4a862..eb6e1881ad 100644 --- a/src/contract-ui/tabs/code/page.tsx +++ b/src/contract-ui/tabs/code/page.tsx @@ -2,7 +2,7 @@ import { CodeOverview } from "./components/code-overview"; import { Flex } from "@chakra-ui/react"; import { useContract } from "@thirdweb-dev/react"; import { Abi } from "@thirdweb-dev/sdk"; -import { useEffect } from "react"; +import { useLayoutEffect } from "react"; interface ContractCodePageProps { contractAddress?: string; @@ -13,7 +13,7 @@ export const ContractCodePage: React.FC = ({ }) => { const contractQuery = useContract(contractAddress); - useEffect(() => { + useLayoutEffect(() => { window?.scrollTo({ top: 0, behavior: "smooth" }); }, []); diff --git a/src/contract-ui/tabs/embed/components/embed-setup.tsx b/src/contract-ui/tabs/embed/components/embed-setup.tsx index eff89ca939..d78f074d56 100644 --- a/src/contract-ui/tabs/embed/components/embed-setup.tsx +++ b/src/contract-ui/tabs/embed/components/embed-setup.tsx @@ -14,7 +14,7 @@ import { configureChain, minimizeChain } from "@thirdweb-dev/chains"; import { DropContract } from "@thirdweb-dev/react"; import { useTrack } from "hooks/analytics/useTrack"; import { useSupportedChainsRecord } from "hooks/chains/configureChains"; -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import { useForm } from "react-hook-form"; import { FiCopy } from "react-icons/fi"; import { @@ -256,13 +256,7 @@ export const EmbedSetup: React.FC = ({ [iframeSrc], ); - const { hasCopied, onCopy, setValue } = useClipboard(embedCode, 3000); - - useEffect(() => { - if (embedCode) { - setValue(embedCode); - } - }, [embedCode, setValue]); + const { hasCopied, onCopy } = useClipboard(embedCode, 3000); return ( diff --git a/src/contract-ui/tabs/events/components/events-feed.tsx b/src/contract-ui/tabs/events/components/events-feed.tsx index b14b125459..9bba17ba34 100644 --- a/src/contract-ui/tabs/events/components/events-feed.tsx +++ b/src/contract-ui/tabs/events/components/events-feed.tsx @@ -28,7 +28,7 @@ import type { ContractEvent } from "@thirdweb-dev/sdk"; import { AnimatePresence, motion } from "framer-motion"; import { useSingleQueryParam } from "hooks/useQueryParam"; import { useRouter } from "next/router"; -import { useEffect, useMemo, useState, Fragment } from "react"; +import { useMemo, useState, Fragment } from "react"; import { FiChevronDown, FiCopy } from "react-icons/fi"; import { Button, @@ -199,13 +199,7 @@ const EventsFeedItem: React.FC = ({ chainSlug, }) => { const toast = useToast(); - const { onCopy, setValue } = useClipboard(transaction.transactionHash); - - useEffect(() => { - if (transaction.transactionHash) { - setValue(transaction.transactionHash); - } - }, [transaction.transactionHash, setValue]); + const { onCopy } = useClipboard(transaction.transactionHash); const router = useRouter(); diff --git a/src/contract-ui/tabs/events/page.tsx b/src/contract-ui/tabs/events/page.tsx index 59dc0fd861..f542d467c9 100644 --- a/src/contract-ui/tabs/events/page.tsx +++ b/src/contract-ui/tabs/events/page.tsx @@ -1,6 +1,6 @@ import { EventsFeed } from "./components/events-feed"; import { Flex } from "@chakra-ui/react"; -import { useEffect } from "react"; +import { useLayoutEffect } from "react"; interface ContractEventsPageProps { contractAddress?: string; @@ -9,7 +9,7 @@ interface ContractEventsPageProps { export const ContractEventsPage: React.FC = ({ contractAddress, }) => { - useEffect(() => { + useLayoutEffect(() => { window?.scrollTo({ top: 0, behavior: "smooth" }); }, []); diff --git a/src/contract-ui/tabs/listings/components/table.tsx b/src/contract-ui/tabs/listings/components/table.tsx index 769acc1cec..dff94c2dfe 100644 --- a/src/contract-ui/tabs/listings/components/table.tsx +++ b/src/contract-ui/tabs/listings/components/table.tsx @@ -150,6 +150,8 @@ export const ListingsTable: React.FC = ({ contract }) => { usePagination, ); + // FIXME: re-work tables and pagination with @tanstack/table@latest - which (I believe) does not need this workaround anymore + // eslint-disable-next-line no-restricted-syntax useEffect(() => { setQueryParams({ start: pageIndex * pageSize, count: pageSize }); }, [pageIndex, pageSize]); diff --git a/src/contract-ui/tabs/nfts/components/airdrop-upload.tsx b/src/contract-ui/tabs/nfts/components/airdrop-upload.tsx index c417d16646..eb0f99cf62 100644 --- a/src/contract-ui/tabs/nfts/components/airdrop-upload.tsx +++ b/src/contract-ui/tabs/nfts/components/airdrop-upload.tsx @@ -110,6 +110,8 @@ export const AirdropUpload: React.FC = ({ [], ); + // FIXME: this can be a mutation or query insead! + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (validAirdrop.length === 0) { return setAirdropData([]); diff --git a/src/contract-ui/tabs/nfts/components/table.tsx b/src/contract-ui/tabs/nfts/components/table.tsx index f5fa116b04..49127b37d3 100644 --- a/src/contract-ui/tabs/nfts/components/table.tsx +++ b/src/contract-ui/tabs/nfts/components/table.tsx @@ -202,6 +202,8 @@ export const NFTGetAllTable: React.FC = ({ usePagination, ); + // FIXME: re-work tables and pagination with @tanstack/table@latest - which (I believe) does not need this workaround anymore + // eslint-disable-next-line no-restricted-syntax useEffect(() => { setQueryParams({ start: pageIndex * pageSize, count: pageSize }); }, [pageIndex, pageSize]); diff --git a/src/contract-ui/tabs/overview/components/LatestEvents.tsx b/src/contract-ui/tabs/overview/components/LatestEvents.tsx index 88fe53b100..837aece2b9 100644 --- a/src/contract-ui/tabs/overview/components/LatestEvents.tsx +++ b/src/contract-ui/tabs/overview/components/LatestEvents.tsx @@ -16,7 +16,7 @@ import { import type { ContractEvent } from "@thirdweb-dev/sdk"; import { useTabHref } from "contract-ui/utils"; import { AnimatePresence, motion } from "framer-motion"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { FiCopy } from "react-icons/fi"; import { Button, @@ -118,13 +118,7 @@ interface EventsFeedItemProps { const EventsFeedItem: React.FC = ({ transaction }) => { const toast = useToast(); - const { onCopy, setValue } = useClipboard(transaction.transactionHash); - - useEffect(() => { - if (transaction.transactionHash) { - setValue(transaction.transactionHash); - } - }, [transaction.transactionHash, setValue]); + const { onCopy } = useClipboard(transaction.transactionHash); const href = useTabHref("events"); diff --git a/src/contract-ui/tabs/overview/components/PermissionsTable.tsx b/src/contract-ui/tabs/overview/components/PermissionsTable.tsx index 82759ea094..6305896687 100644 --- a/src/contract-ui/tabs/overview/components/PermissionsTable.tsx +++ b/src/contract-ui/tabs/overview/components/PermissionsTable.tsx @@ -16,7 +16,7 @@ import { SmartContract } from "@thirdweb-dev/sdk"; import { useTabHref } from "contract-ui/utils"; import { constants } from "ethers"; import { AnimatePresence, motion } from "framer-motion"; -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import { FiCopy } from "react-icons/fi"; import { Button, @@ -125,13 +125,7 @@ interface PermissionsItemProps { const PermissionsItem: React.FC = ({ data }) => { const toast = useToast(); - const { onCopy, setValue } = useClipboard(data.member); - - useEffect(() => { - if (data.member) { - setValue(data.member); - } - }, [data.member, setValue]); + const { onCopy } = useClipboard(data.member); return ( diff --git a/src/contract-ui/tabs/payments/page.tsx b/src/contract-ui/tabs/payments/page.tsx index c57b28a5b3..4b22a2cefe 100644 --- a/src/contract-ui/tabs/payments/page.tsx +++ b/src/contract-ui/tabs/payments/page.tsx @@ -4,7 +4,7 @@ import { usePaymentsEnabledContracts, } from "@3rdweb-sdk/react/hooks/usePayments"; import { Center, Flex, Spinner, Stack } from "@chakra-ui/react"; -import { useEffect, useMemo } from "react"; +import { useLayoutEffect, useMemo } from "react"; import { PaymentsAnalytics } from "./components/payments-analytics"; import { PaymentCheckouts } from "./components/payments-checkouts"; import { Card, Heading, Text } from "tw-components"; @@ -31,7 +31,7 @@ export const ContractPaymentsPage: React.FC = ({ isError, } = usePaymentsEnabledContracts(); - useEffect(() => { + useLayoutEffect(() => { window?.scrollTo({ top: 0, behavior: "smooth" }); }, []); diff --git a/src/contract-ui/tabs/permissions/page.tsx b/src/contract-ui/tabs/permissions/page.tsx index 21adb20bc2..c99f9ef4c9 100644 --- a/src/contract-ui/tabs/permissions/page.tsx +++ b/src/contract-ui/tabs/permissions/page.tsx @@ -3,7 +3,7 @@ import { ButtonGroup, Code, Divider, Flex } from "@chakra-ui/react"; import { useContract } from "@thirdweb-dev/react"; import { detectFeatures } from "components/contract-components/utils"; import { useTabHref } from "contract-ui/utils"; -import { useEffect } from "react"; +import { useLayoutEffect } from "react"; import { Card, Heading, Link, LinkButton, Text } from "tw-components"; interface ContractPermissionsPageProps { @@ -13,7 +13,7 @@ interface ContractPermissionsPageProps { export const ContractPermissionsPage: React.FC< ContractPermissionsPageProps > = ({ contractAddress }) => { - useEffect(() => { + useLayoutEffect(() => { window?.scrollTo({ top: 0, behavior: "smooth" }); }, []); diff --git a/src/contract-ui/tabs/shared-components/marketplace-table.tsx b/src/contract-ui/tabs/shared-components/marketplace-table.tsx index 8b5df464f4..d03cd56979 100644 --- a/src/contract-ui/tabs/shared-components/marketplace-table.tsx +++ b/src/contract-ui/tabs/shared-components/marketplace-table.tsx @@ -162,6 +162,8 @@ export const MarketplaceTable: React.FC = ({ usePagination, ); + // FIXME: re-work tables and pagination with @tanstack/table@latest - which (I believe) does not need this workaround anymore + // eslint-disable-next-line no-restricted-syntax useEffect(() => { setQueryParams({ start: pageIndex * pageSize, count: pageSize }); }, [pageIndex, pageSize, setQueryParams]); diff --git a/src/core-ui/batch-upload/progress-box.tsx b/src/core-ui/batch-upload/progress-box.tsx index aae247fa1a..bc19b095cf 100644 --- a/src/core-ui/batch-upload/progress-box.tsx +++ b/src/core-ui/batch-upload/progress-box.tsx @@ -11,6 +11,8 @@ export const ProgressBox: React.FC = ({ progress }) => { const isFinished = progress.progress >= progress.total; const [takingLong, setTakingLong] = useState(false); + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (isFinished) { const t = setTimeout(() => { diff --git a/src/core-ui/sidebar/tunnel.tsx b/src/core-ui/sidebar/tunnel.tsx index 29131ddba7..6b758e541d 100644 --- a/src/core-ui/sidebar/tunnel.tsx +++ b/src/core-ui/sidebar/tunnel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useLayoutEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { ComponentWithChildren } from "types/component-with-children"; @@ -21,7 +21,7 @@ const ClientOnlyPortal: ComponentWithChildren = ({ const ref = useRef(null); const [mounted, setMounted] = useState(false); - useEffect(() => { + useLayoutEffect(() => { ref.current = document.getElementById(selector); setMounted(true); }, [selector]); diff --git a/src/hooks/common/useDebounce.ts b/src/hooks/common/useDebounce.ts deleted file mode 100644 index 173bc0e5c3..0000000000 --- a/src/hooks/common/useDebounce.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useState } from "react"; - -export function useDebounce(value: T, delay: number): T { - // State and setters for debounced value - const [debouncedValue, setDebouncedValue] = useState(value); - useEffect(() => { - // Update debounced value after delay - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - // Cancel the timeout if value changes (also on delay change or unmount) - // This is how we prevent debounced value from updating if value is changed ... - // .. within the delay period. Timeout gets cleared and restarted. - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - return debouncedValue; -} diff --git a/src/hooks/effect/useParallexEffect.tsx b/src/hooks/effect/useParallexEffect.tsx index 1da8c25c77..dbbe49d5a6 100644 --- a/src/hooks/effect/useParallexEffect.tsx +++ b/src/hooks/effect/useParallexEffect.tsx @@ -3,6 +3,8 @@ import { useEffect, useState } from "react"; export const useParallaxEffect = (speed = 0.5) => { const [offsetY, setOffsetY] = useState(0); + // legit use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { function handleScroll() { setOffsetY(window.scrollY); diff --git a/src/hooks/useDelayedDisplay.ts b/src/hooks/useDelayedDisplay.ts deleted file mode 100644 index 9f91cfbdb0..0000000000 --- a/src/hooks/useDelayedDisplay.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useState } from "react"; - -const useDelayedDisplay = (delay: number) => { - const [displayContent, setDisplayContent] = useState(false); - - useEffect(() => { - const timer = setTimeout(() => { - setDisplayContent(true); - }, delay); - - return () => { - clearTimeout(timer); - }; - }, [delay]); - - return displayContent; -}; - -export default useDelayedDisplay; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts index a29a24958e..d166194e76 100644 --- a/src/hooks/useLocalStorage.ts +++ b/src/hooks/useLocalStorage.ts @@ -8,6 +8,8 @@ export function useLocalStorage( ) { const [value, _setValue] = useState(serverSideFallback); + // FIXME: ideally we do not need localstorage like this, alernatively we move this into use-query and use-mutation to invalidate etc + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const item = window.localStorage.getItem(key); diff --git a/src/hooks/useNativeColorMode.ts b/src/hooks/useNativeColorMode.ts index 0df42371f3..2e3689d792 100644 --- a/src/hooks/useNativeColorMode.ts +++ b/src/hooks/useNativeColorMode.ts @@ -1,9 +1,9 @@ import { useColorModeValue } from "@chakra-ui/react"; -import { useEffect } from "react"; +import { useLayoutEffect } from "react"; export function useNativeColorMode() { const activeColorMode = useColorModeValue("light", "dark"); - useEffect(() => { + useLayoutEffect(() => { document .getElementById("tw-body-root") ?.style.setProperty("color-scheme", activeColorMode); diff --git a/src/pages/[chain_id]/[...paths].tsx b/src/pages/[chain_id]/[...paths].tsx index 1c52171cce..886cf0df7c 100644 --- a/src/pages/[chain_id]/[...paths].tsx +++ b/src/pages/[chain_id]/[...paths].tsx @@ -72,6 +72,8 @@ const ContractPage: ThirdwebNextPage = () => { const supportedChainsSlugRecord = useSupportedChainsSlugRecord(); const configuredChainsRecord = useSupportedChainsRecord(); + // this will go away as part of the RSC rewrite! + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (!isSupportedChainsReady || !chainSlug) { return; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2a6b8d757a..409a2d9525 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -60,6 +60,8 @@ const ConsoleAppWrapper: React.FC = ({ const router = useRouter(); const { shouldReload } = useBuildId(); + // legit use-case, will go away as part of app router rewrite (once finished) + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const handleRouteChange = async () => { if (shouldReload()) { @@ -74,6 +76,8 @@ const ConsoleAppWrapper: React.FC = ({ }; }, [router, shouldReload]); + // legit use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // Taken from StackOverflow. Trying to detect both Safari desktop and mobile. const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); @@ -89,6 +93,8 @@ const ConsoleAppWrapper: React.FC = ({ } }, []); + // legit use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // Init PostHog Cloud (Used for surveys) posthogCloud.init( @@ -135,6 +141,8 @@ const ConsoleAppWrapper: React.FC = ({ // starts out with "none" page id const prevPageId = useRef(PageId.None); + // legit use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // this catches the case where the the hook is called twice on the same page if (pageId === prevPageId.current) { diff --git a/src/pages/cli/login.tsx b/src/pages/cli/login.tsx index e4b3de7c6e..debd419f13 100644 --- a/src/pages/cli/login.tsx +++ b/src/pages/cli/login.tsx @@ -16,12 +16,13 @@ import { Switch, VStack, } from "@chakra-ui/react"; +import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@thirdweb-dev/react"; import { AppLayout } from "components/app-layouts/app"; import { useTrack } from "hooks/analytics/useTrack"; import { useRouter } from "next/router"; import { PageId } from "page-id"; -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useState } from "react"; import { PiWarningFill } from "react-icons/pi"; import { Button, Card, FormLabel, Heading, Link, Text } from "tw-components"; import { ThirdwebNextPage } from "utils/types"; @@ -34,7 +35,7 @@ const LoginPage: ThirdwebNextPage = () => { const [deviceName, setDeviceName] = useState(""); const [rejected, setRejected] = useState(false); const trackEvent = useTrack(); - const [isBrave, setIsBrave] = useState(false); + const [hasRemovedShield, setHasRemovedShield] = useState(false); const { isLoggedIn } = useLoggedInUser(); @@ -46,14 +47,14 @@ const LoginPage: ThirdwebNextPage = () => { ); const [success, setSuccess] = useState(false); - useEffect(() => { - (async () => { - const isBraveInNavigator = - // @ts-expect-error - brave is not in the types - navigator?.brave && (await navigator?.brave.isBrave()); - setIsBrave(!!isBraveInNavigator); - })(); - }, []); + const isBrave = useQuery({ + queryKey: ["is-brave-browser"], + initialData: false, + queryFn: async () => { + // @ts-expect-error - brave is not in the types + return navigator?.brave && (await navigator?.brave.isBrave()); + }, + }); const generateToken = async () => { if (!payload) { diff --git a/src/pages/contracts/deploy/[contractId].tsx b/src/pages/contracts/deploy/[contractId].tsx index 0a3b50b16a..64e61ebb4c 100644 --- a/src/pages/contracts/deploy/[contractId].tsx +++ b/src/pages/contracts/deploy/[contractId].tsx @@ -37,6 +37,8 @@ const ContractDeployDetailPage: ThirdwebNextPage = () => { ); const hasTrackedImpression = useRef(false); + // TODO: find better way to do impression tracking + // eslint-disable-next-line no-restricted-syntax useEffect(() => { if (publishMetadataQuery.data && !hasTrackedImpression.current) { hasTrackedImpression.current = true; diff --git a/src/pages/dashboard/connect/account-abstraction.tsx b/src/pages/dashboard/connect/account-abstraction.tsx index 36015320e1..53403a5843 100644 --- a/src/pages/dashboard/connect/account-abstraction.tsx +++ b/src/pages/dashboard/connect/account-abstraction.tsx @@ -26,7 +26,7 @@ import { useAccount, useApiKeys, } from "@3rdweb-sdk/react/hooks/useApi"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { SmartWalletsBillingAlert } from "components/settings/ApiKeys/Alerts"; import { NextSeo } from "next-seo"; import { getAbsoluteUrl } from "lib/vercel-utils"; @@ -50,7 +50,7 @@ const DashboardConnectAccountAbstraction: ThirdwebNextPage = () => { const defaultClientId = router.query.clientId?.toString(); const { isLoggedIn } = useLoggedInUser(); const keysQuery = useApiKeys(); - const [selectedKey, setSelectedKey] = useState(); + const [selectedKey_, setSelectedKey] = useState(); const meQuery = useAccount(); const account = meQuery?.data; @@ -62,25 +62,19 @@ const DashboardConnectAccountAbstraction: ThirdwebNextPage = () => { const hasApiKeys = apiKeys.length > 0; - useEffect(() => { - if (selectedKey) { - return; + // compute the actual selected key based on if there is a state, if there is a query param, or otherwise the first one + const selectedKey = useMemo(() => { + if (selectedKey_) { + return selectedKey_; } - if (apiKeys.length > 0) { + if (apiKeys.length) { if (defaultClientId) { - const key = apiKeys.find((k) => k.key === defaultClientId); - if (key) { - setSelectedKey(key); - } else { - setSelectedKey(apiKeys[0]); - } - } else { - setSelectedKey(apiKeys[0]); + return apiKeys.find((k) => k.key === defaultClientId); } - } else { - setSelectedKey(undefined); + return apiKeys[0]; } - }, [apiKeys, selectedKey, defaultClientId]); + return undefined; + }, [apiKeys, defaultClientId, selectedKey_]); const hasSmartWalletsWithoutBilling = useMemo(() => { if (!account || !apiKeys) { diff --git a/src/pages/dashboard/connect/analytics.tsx b/src/pages/dashboard/connect/analytics.tsx index b318ae7b4b..538951986b 100644 --- a/src/pages/dashboard/connect/analytics.tsx +++ b/src/pages/dashboard/connect/analytics.tsx @@ -12,7 +12,7 @@ import { ConnectSidebar } from "core-ui/sidebar/connect"; import { PageId } from "page-id"; import { ThirdwebNextPage } from "utils/types"; import { Card, Heading, LinkButton, Text } from "tw-components"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { ApiKey, useApiKeys, @@ -36,28 +36,40 @@ import { useLoggedInUser } from "@3rdweb-sdk/react/hooks/useLoggedInUser"; import { ApiKeysMenu } from "components/settings/ApiKeys/Menu"; import { ConnectWalletPrompt } from "components/settings/ConnectWalletPrompt"; import { GatedFeature } from "components/settings/Account/Billing/GatedFeature"; +import { useRouter } from "next/router"; const RADIAN = Math.PI / 180; const DashboardConnectAnalytics: ThirdwebNextPage = () => { + const router = useRouter(); + const defaultClientId = router.query.clientId?.toString(); const { colorMode } = useColorMode(); const isMobile = useBreakpointValue({ base: true, md: false }); const { isLoggedIn } = useLoggedInUser(); const keysQuery = useApiKeys(); - const [selectedKey, setSelectedKey] = useState(); - const statsQuery = useWalletStats(selectedKey?.key); + const [selectedKey_, setSelectedKey] = useState(); + const [activeIndex, setActiveIndex] = useState(0); - useEffect(() => { - if (selectedKey) { - return; + const apiKeys = useMemo(() => { + return keysQuery?.data || []; + }, [keysQuery]); + + // compute the actual selected key based on if there is a state, if there is a query param, or otherwise the first one + const selectedKey = useMemo(() => { + if (selectedKey_) { + return selectedKey_; } - if (keysQuery.data && keysQuery.data?.length > 0) { - setSelectedKey(keysQuery.data?.[0]); - } else { - setSelectedKey(undefined); + if (apiKeys.length) { + if (defaultClientId) { + return apiKeys.find((k) => k.key === defaultClientId); + } + return apiKeys[0]; } - }, [keysQuery.data, selectedKey]); + return undefined; + }, [apiKeys, defaultClientId, selectedKey_]); + + const statsQuery = useWalletStats(selectedKey?.key); const barColors = useMemo( () => (colorMode === "light" ? BAR_COLORS_LIGHT : BAR_COLORS_DARK), diff --git a/src/pages/dashboard/connect/in-app-wallets.tsx b/src/pages/dashboard/connect/in-app-wallets.tsx index 6dd62f40be..0e355183d8 100644 --- a/src/pages/dashboard/connect/in-app-wallets.tsx +++ b/src/pages/dashboard/connect/in-app-wallets.tsx @@ -5,7 +5,7 @@ import { AppLayout } from "components/app-layouts/app"; import { EmbeddedWallets } from "components/embedded-wallets"; import { ConnectSidebar } from "core-ui/sidebar/connect"; import { PageId } from "page-id"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { Card, Heading, Text, TrackedLink } from "tw-components"; import { ThirdwebNextPage } from "utils/types"; import { NoApiKeys } from "components/settings/ApiKeys/NoApiKeys"; @@ -25,8 +25,7 @@ const DashboardConnectEmbeddedWallets: ThirdwebNextPage = () => { const { isLoggedIn } = useLoggedInUser(); const keysQuery = useApiKeys(); - const [selectedKey, setSelectedKey] = useState(); - const walletsQuery = useEmbeddedWallets(selectedKey?.key as string); + const [selectedKey_, setSelectedKey] = useState(); const apiKeys = useMemo(() => { return (keysQuery?.data || []).filter((key) => { @@ -36,28 +35,25 @@ const DashboardConnectEmbeddedWallets: ThirdwebNextPage = () => { }); }, [keysQuery]); - const wallets = walletsQuery?.data || []; const hasApiKeys = apiKeys.length > 0; - useEffect(() => { - if (selectedKey) { - return; + // compute the actual selected key based on if there is a state, if there is a query param, or otherwise the first one + const selectedKey = useMemo(() => { + if (selectedKey_) { + return selectedKey_; } - if (apiKeys.length > 0) { + if (apiKeys.length) { if (defaultClientId) { - const key = apiKeys.find((k) => k.key === defaultClientId); - if (key) { - setSelectedKey(key); - } else { - setSelectedKey(apiKeys[0]); - } - } else { - setSelectedKey(apiKeys[0]); + return apiKeys.find((k) => k.key === defaultClientId); } - } else { - setSelectedKey(undefined); + return apiKeys[0]; } - }, [apiKeys, selectedKey, defaultClientId]); + return undefined; + }, [apiKeys, defaultClientId, selectedKey_]); + + const walletsQuery = useEmbeddedWallets(selectedKey?.key as string); + + const wallets = walletsQuery?.data || []; if (!isLoggedIn) { return ; diff --git a/src/pages/dashboard/connect/index.tsx b/src/pages/dashboard/connect/index.tsx deleted file mode 100644 index f44aef4088..0000000000 --- a/src/pages/dashboard/connect/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useEffect } from "react"; -import { useRouter } from "next/router"; - -const DashboardConnect = () => { - const router = useRouter(); - - useEffect(() => { - router.push("/dashboard/connect/playground"); - }, [router]); - - return null; -}; - -export default DashboardConnect; diff --git a/src/pages/dashboard/connect/pay.tsx b/src/pages/dashboard/connect/pay.tsx index 535ce492fe..db15d53dbd 100644 --- a/src/pages/dashboard/connect/pay.tsx +++ b/src/pages/dashboard/connect/pay.tsx @@ -17,7 +17,7 @@ import { import { AppLayout } from "components/app-layouts/app"; import { ConnectSidebar } from "core-ui/sidebar/connect"; import { PageId } from "page-id"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Heading, Text, TrackedLink } from "tw-components"; import { ApiKeysMenu } from "components/settings/ApiKeys/Menu"; import { NoApiKeys } from "components/settings/ApiKeys/NoApiKeys"; @@ -94,20 +94,26 @@ function RadioCard(props: UseRadioProps & BoxProps) { ); } -const usePayConfig = () => { +function usePayConfig() { const router = useRouter(); const defaultClientId = router.query.clientId?.toString(); const { user } = useLoggedInUser(); const queryClient = useQueryClient(); - const [selectedKey, setSelectedKey] = useState(); + const [selectedKey_, setSelectedKey] = useState(); const keysQuery = useApiKeys(); - const apiKeysData = (keysQuery?.data ?? []).filter((key) => { - return !!(key.services ?? []).find((srv) => srv.name === "pay"); - }); + const apiKeysData = useMemo( + () => + (keysQuery?.data ?? []).filter((key) => { + return !!(key.services ?? []).find((srv) => srv.name === "pay"); + }), + [keysQuery?.data], + ); const hasPayApiKeys = apiKeysData.length > 0; + // FIXME: this seems like a deeper problem, solve later + // eslint-disable-next-line no-restricted-syntax useEffect(() => { // query rehydrates from cache leading to stale results if user refreshes shortly after updating their dashboard. // Invalidate the query to force a refetch @@ -116,20 +122,19 @@ const usePayConfig = () => { } }, [queryClient, user?.address]); - useEffect(() => { - if (defaultClientId) { - const key = apiKeysData.find((k) => k.key === defaultClientId); - if (key) { - setSelectedKey(key); - return; + // compute the actual selected key based on if there is a state, if there is a query param, or otherwise the first one + const selectedKey = useMemo(() => { + if (selectedKey_) { + return selectedKey_; + } + if (apiKeysData.length) { + if (defaultClientId) { + return apiKeysData.find((k) => k.key === defaultClientId); } + return apiKeysData[0]; } - setSelectedKey( - selectedKey - ? apiKeysData.find((apiKey) => apiKey.id === selectedKey.id) - : apiKeysData[0], - ); - }, [selectedKey, defaultClientId, apiKeysData]); + return undefined; + }, [apiKeysData, defaultClientId, selectedKey_]); return { hasPayApiKeys, @@ -138,17 +143,13 @@ const usePayConfig = () => { apiKeysData, hasApiKeys: !!keysQuery.data?.length, }; -}; +} -const useTabConfig = () => { - const [tabOption, setTabOption] = useState<"pay" | "checkouts">("pay"); +function useTabConfig() { const router = useRouter(); - - useEffect(() => { - if (router.query.tab === "checkouts") { - setTabOption("checkouts"); - } - }, [router.query.tab]); + const [tabOption, setTabOption] = useState<"pay" | "checkouts">( + router.query.tab === "checkouts" ? "checkouts" : "pay", + ); const { data: paymentEnabledContracts } = usePaymentsEnabledContracts(); const radioOptions = ["pay", "checkouts"].filter((option) => { @@ -158,7 +159,7 @@ const useTabConfig = () => { ); }); return { tabOption, setTabOption, radioOptions }; -}; +} const DashboardConnectPay: ThirdwebNextPage = () => { const { tabOption, setTabOption, radioOptions } = useTabConfig(); diff --git a/src/pages/dashboard/connect/playground.tsx b/src/pages/dashboard/connect/playground.tsx index 3dc0273aae..413d8ae342 100644 --- a/src/pages/dashboard/connect/playground.tsx +++ b/src/pages/dashboard/connect/playground.tsx @@ -211,28 +211,30 @@ function GuideLink(props: { ); } +const bannerPosters = [ + { + img: require("../../../../public/assets/connect/pixels-cw.png"), + position: "flex-end", + padding: 0, + }, + { + img: require("../../../../public/assets/connect/w3w-cw.png"), + position: "center", + padding: 4, + }, + { + img: require("../../../../public/assets/connect/lv-cw.png"), + position: "flex-end", + padding: 0, + }, +]; + function BuildCustomBanner() { const [bannerImageIndex, setBannerImageIndex] = useState(1); - const bannerPosters = [ - { - img: require("../../../../public/assets/connect/pixels-cw.png"), - position: "flex-end", - padding: 0, - }, - { - img: require("../../../../public/assets/connect/w3w-cw.png"), - position: "center", - padding: 4, - }, - { - img: require("../../../../public/assets/connect/lv-cw.png"), - position: "flex-end", - padding: 0, - }, - ]; - // update banner image every 3 seconds + // legitimate use-case + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const interval = setInterval(() => { setBannerImageIndex((prev) => { @@ -244,7 +246,7 @@ function BuildCustomBanner() { }); }, 3000); return () => clearInterval(interval); - }, [bannerPosters.length]); + }, []); return ( { - const [apiKey, setApiKey] = useState(null); const [editing, setEditing] = useState(false); const keysQuery = useApiKeys(); const router = useRouter(); const { user } = useLoggedInUser(); + const apiKey = useMemo(() => { + return keysQuery.data?.find((key) => key.id === router.query.id); + }, [keysQuery?.data, router.query.id]); + + // legitimate use-case, however with move to RSCs this will happen in the RSC logic later + // eslint-disable-next-line no-restricted-syntax useEffect(() => { - if (keysQuery?.data) { - const activeKey = keysQuery?.data.find( - (key) => key.id === router.query.id, - ); - - if (!activeKey) { - router.push("/dashboard/settings/api-keys"); - return; - } - setApiKey(activeKey); + if (keysQuery.isSuccess && !apiKey) { + router.push("/dashboard/settings/api-keys"); } - }, [keysQuery?.data, router]); + }, [apiKey, keysQuery.isSuccess, router]); if (!user) { return ; diff --git a/src/pages/dashboard/settings/billing.tsx b/src/pages/dashboard/settings/billing.tsx index 1fbd6334c0..ad16b3bd85 100644 --- a/src/pages/dashboard/settings/billing.tsx +++ b/src/pages/dashboard/settings/billing.tsx @@ -24,14 +24,15 @@ const SettingsBillingPage: ThirdwebNextPage = () => { const router = useRouter(); const { data: account } = meQuery; - useEffect(() => { - const { payment_intent, source_redirect_slug } = router.query; + const { payment_intent, source_redirect_slug } = router.query; + // legit usecase, however will move to server side with RSCs + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { if (payment_intent || source_redirect_slug) { router.replace("/dashboard/settings/billing"); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router]); + }, [payment_intent, router, source_redirect_slug]); if (!isLoading && !isLoggedIn) { return ; diff --git a/src/pages/dashboard/settings/index.tsx b/src/pages/dashboard/settings/index.tsx deleted file mode 100644 index ad064998e7..0000000000 --- a/src/pages/dashboard/settings/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useEffect } from "react"; -import { useRouter } from "next/router"; - -const SettingsPage = () => { - const router = useRouter(); - - useEffect(() => { - router.push("/dashboard/settings/api-keys"); - }, [router]); - - return null; -}; - -export default SettingsPage; diff --git a/src/pages/frame/degen/mint.tsx b/src/pages/frame/degen/mint.tsx index 7e2b0a7daf..786e61691a 100644 --- a/src/pages/frame/degen/mint.tsx +++ b/src/pages/frame/degen/mint.tsx @@ -12,6 +12,8 @@ const ogImageUrl = `${getAbsoluteUrl()}/assets/og-image/degen-enchine-frame.png` const BaseFramePage = () => { const router = useRouter(); + // this can move to RSC logic (depending on user agent likely) + // eslint-disable-next-line no-restricted-syntax useEffect(() => { router.push("https://frames.thirdweb.com/degen"); }, [router]); diff --git a/src/pages/frame/mint/base/index.tsx b/src/pages/frame/mint/base/index.tsx index 8b33d64e89..d6e7e3430f 100644 --- a/src/pages/frame/mint/base/index.tsx +++ b/src/pages/frame/mint/base/index.tsx @@ -13,6 +13,8 @@ const ogImageUrl = `${getAbsoluteUrl()}/assets/og-image/tx-frame-og-image.png`; const BaseFramePage = () => { const router = useRouter(); + // this can move to RSC logic (depending on user agent likely) + // eslint-disable-next-line no-restricted-syntax useEffect(() => { router.push("https://blog.thirdweb.com/guides/farcaster-transaction-frame"); }, [router]); diff --git a/src/pages/profile/[profileAddress].tsx b/src/pages/profile/[profileAddress].tsx index 873fb1ab21..7b1989e5e5 100644 --- a/src/pages/profile/[profileAddress].tsx +++ b/src/pages/profile/[profileAddress].tsx @@ -43,6 +43,9 @@ const UserPage: ThirdwebNextPage = (props: UserPageProps) => { const router = useRouter(); // We do this so it doesn't break for users that haven't updated their CLI + + // TODO: re-visit, do we still need to do this? + // eslint-disable-next-line no-restricted-syntax useEffect(() => { const previousPath = router.asPath.split("/")[2]; if ( diff --git a/src/tw-components/AddressCopyButton.tsx b/src/tw-components/AddressCopyButton.tsx index 4b2f1dc297..33e05cfdd8 100644 --- a/src/tw-components/AddressCopyButton.tsx +++ b/src/tw-components/AddressCopyButton.tsx @@ -8,7 +8,6 @@ import { Card } from "./card"; import { Text } from "./text"; import { Icon, Tooltip, useClipboard, useToast } from "@chakra-ui/react"; import { useTrack } from "hooks/analytics/useTrack"; -import { useEffect } from "react"; import { FiCopy } from "react-icons/fi"; interface AddressCopyButtonProps extends Omit { @@ -45,13 +44,7 @@ export const AddressCopyButton: React.FC = ({ title = "address", ...restButtonProps }) => { - const { onCopy, setValue } = useClipboard(address || ""); - - useEffect(() => { - if (address) { - setValue(address); - } - }, [address, setValue]); + const { onCopy } = useClipboard(address || ""); const trackEvent = useTrack(); const toast = useToast(); diff --git a/src/tw-components/button.tsx b/src/tw-components/button.tsx index a1d4f676f6..3c5aa32d16 100644 --- a/src/tw-components/button.tsx +++ b/src/tw-components/button.tsx @@ -13,7 +13,7 @@ import { useClipboard, } from "@chakra-ui/react"; import { useTrack } from "hooks/analytics/useTrack"; -import { useEffect, forwardRef as reactForwardRef } from "react"; +import { forwardRef as reactForwardRef } from "react"; import { FiCheck, FiCopy, FiExternalLink } from "react-icons/fi"; import { fontWeights, letterSpacings, lineHeights } from "theme/typography"; @@ -183,7 +183,7 @@ interface TrackedCopyButtonProps extends TrackedIconButtonProps { export const TrackedCopyButton = forwardRef( ({ value, ...restButtonProps }, ref) => { - const { onCopy, hasCopied, setValue } = useClipboard(value); + const { onCopy, hasCopied } = useClipboard(value); const copy = (e: React.MouseEvent) => { e.preventDefault(); @@ -191,12 +191,6 @@ export const TrackedCopyButton = forwardRef( onCopy(); }; - useEffect(() => { - if (value) { - setValue(value); - } - }, [value, setValue]); - return ( = ({ lightTheme || lightThemeDefault, darkTheme || darkThemeDefault, ); - const { onCopy, hasCopied, setValue } = useClipboard(codeValue || code); - - useEffect(() => { - if (code) { - setValue(codeValue || code); - } - }, [code, codeValue, setValue]); + const { onCopy, hasCopied } = useClipboard(codeValue || code); return ( Date: Fri, 14 Jun 2024 00:39:22 +0000 Subject: [PATCH 05/10] chore: change `no-restricted-syntax` rule for `useEffect` selector to error in .eslintrc.js (#2658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR updates the ESLint configuration to change the severity level of the `no-restricted-syntax` rule for `useEffect` calls from "warn" to "error". ### Detailed summary - Updated `no-restricted-syntax` rule severity level for `useEffect` calls from "warn" to "error" in `.eslintrc.js`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index d7eddc61ce..f7ee5635fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -100,7 +100,7 @@ module.exports = { // turn off deprecated things? "react/react-in-jsx-scope": "off", "no-restricted-syntax": [ - "warn", + "error", { selector: "CallExpression[callee.name='useEffect']", message: From 9656c9cd4a12efc361e41e8fc99be33a3e291d8d Mon Sep 17 00:00:00 2001 From: jnsdls Date: Fri, 14 Jun 2024 00:56:43 +0000 Subject: [PATCH 06/10] feat: display total and filtered chain counts in chain list (#2659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview The focus of this PR is to improve chain filtering and display in the dashboard. ### Detailed summary - Refactored `getChainsToRender` to return more data - Updated data display to show total, filtered, and displayed chains - Improved pagination logic to consider filtered chains > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../(dashboard)/(chain)/chainlist/page.tsx | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/app/(dashboard)/(chain)/chainlist/page.tsx b/src/app/(dashboard)/(chain)/chainlist/page.tsx index dbbdaf1851..15e0002ede 100644 --- a/src/app/(dashboard)/(chain)/chainlist/page.tsx +++ b/src/app/(dashboard)/(chain)/chainlist/page.tsx @@ -107,6 +107,9 @@ async function getChainsToRender(params: SearchParams) { filteredChains.push(chain); } + const totalCount = chains.length; + const filteredCount = filteredChains.length; + if (params.query) { const fuse = new Fuse(filteredChains, { keys: [ @@ -121,13 +124,21 @@ async function getChainsToRender(params: SearchParams) { ], threshold: 0.2, }); - return fuse - .search(params.query, { - limit: DEFAULT_PAGE_SIZE, - }) - .map((e) => e.item); + return { + chainsToRender: fuse + .search(params.query, { + limit: DEFAULT_PAGE_SIZE, + }) + .map((e) => e.item), + totalCount, + filteredCount, + }; } - return filteredChains; + return { + chainsToRender: filteredChains, + totalCount, + filteredCount, + }; } export const metadata: Metadata = { @@ -188,7 +199,9 @@ async function ChainsData(props: { searchParams: SearchParams; activeView: "table" | "grid"; }) { - const chainsToRender = await getChainsToRender(props.searchParams); + const { chainsToRender, totalCount, filteredCount } = await getChainsToRender( + props.searchParams, + ); // pagination const totalPages = Math.ceil(chainsToRender.length / DEFAULT_PAGE_SIZE); @@ -277,6 +290,23 @@ async function ChainsData(props: { {totalPages > 1 && ( )} +
+

+ Showing{" "} + {paginatedChains.length}{" "} + out of{" "} + {filteredCount !== totalCount ? ( + <> + {filteredCount}{" "} + chains that match filters. (Total:{" "} + {totalCount}) + + ) : ( + <> + {totalCount} chains. + + )} +

); } From b5024e6b913b99a8dcdb6a42d7240f90505fb1cf Mon Sep 17 00:00:00 2001 From: jnsdls Date: Fri, 14 Jun 2024 02:10:33 +0000 Subject: [PATCH 07/10] fix: correct toggleService logic in filters.tsx (#2660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR improves the toggleService function in `filters.tsx` to correctly handle service selection in the client filters. ### Detailed summary - Refactored logic to toggle service selection based on existing parameters - Improved handling of adding/removing services in the search parameters > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../chainlist/components/client/filters.tsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/app/(dashboard)/(chain)/chainlist/components/client/filters.tsx b/src/app/(dashboard)/(chain)/chainlist/components/client/filters.tsx index cf7692a5b9..0ccbbf9673 100644 --- a/src/app/(dashboard)/(chain)/chainlist/components/client/filters.tsx +++ b/src/app/(dashboard)/(chain)/chainlist/components/client/filters.tsx @@ -319,20 +319,10 @@ function isServiceActive(searchParams: URLSearchParams, service: string) { } function toggleService(searchParams: URLSearchParams, service: string) { - if (!searchParams.has("service")) { - // default case is that all services are selected so we have to now add all *other* services except for ours - const allServices = products.map((product) => product.id); - const otherServices = allServices.filter((s) => s !== service); - for (const otherService of otherServices) { - searchParams.append("service", otherService); - } + if (searchParams.getAll("service").includes(service)) { + searchParams.delete("service", service); } else { - // we have services selected, so we need to toggle our service - if (searchParams.getAll("service").includes(service)) { - searchParams.delete("service", service); - } else { - searchParams.append("service", service); - } + searchParams.append("service", service); } } From c72a0537c08cb9989302ccd9007aca8cd84e431c Mon Sep 17 00:00:00 2001 From: kien-ngo <26052673+kien-ngo@users.noreply.github.com> Date: Fri, 14 Jun 2024 04:16:37 +0000 Subject: [PATCH 08/10] Fix relayer table fails to load custom chain (#2661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR updates components to handle missing chain names gracefully. ### Detailed summary - Added fallback for missing chain names with "N/A" - Updated chain name display in multiple components - Added `AddressCopyButton` import for contract subscriptions > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../contract-subscriptions-table.tsx | 14 +++++++++++--- .../engine/overview/transactions-table.tsx | 2 +- .../engine/relayer/add-relayer-button.tsx | 2 +- src/components/engine/relayer/relayers-table.tsx | 8 ++++---- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/engine/contract-subscription/contract-subscriptions-table.tsx b/src/components/engine/contract-subscription/contract-subscriptions-table.tsx index 612283d724..16f71d7286 100644 --- a/src/components/engine/contract-subscription/contract-subscriptions-table.tsx +++ b/src/components/engine/contract-subscription/contract-subscriptions-table.tsx @@ -32,6 +32,7 @@ import { useState } from "react"; import { FiInfo, FiTrash } from "react-icons/fi"; import { defineChain, eth_getBlockByNumber, getRpcClient } from "thirdweb"; import { Button, Card, FormLabel, LinkButton, Text } from "tw-components"; +import { AddressCopyButton } from "../../../tw-components/AddressCopyButton"; interface ContractSubscriptionTableProps { instanceUrl: string; @@ -65,7 +66,7 @@ export const ContractSubscriptionTable: React.FC< return ( - {chain.name} + {chain?.name ?? "N/A"} ); }, @@ -76,7 +77,14 @@ export const ContractSubscriptionTable: React.FC< const { chainId } = cell.row.original; const chain = chainIdToChainRecord[chainId]; const explorer = chain?.explorers?.[0]; - + if (!explorer) { + return ( + + ); + } return ( Chain - {chain.name} + {chain?.name ?? "N/A"} diff --git a/src/components/engine/overview/transactions-table.tsx b/src/components/engine/overview/transactions-table.tsx index 637bc839af..d4af9527d6 100644 --- a/src/components/engine/overview/transactions-table.tsx +++ b/src/components/engine/overview/transactions-table.tsx @@ -130,7 +130,7 @@ export const TransactionsTable: React.FC = ({ - {chain.name} + {chain?.name ?? "N/A"} ); diff --git a/src/components/engine/relayer/add-relayer-button.tsx b/src/components/engine/relayer/add-relayer-button.tsx index 9edc7a502a..6d139f24d8 100644 --- a/src/components/engine/relayer/add-relayer-button.tsx +++ b/src/components/engine/relayer/add-relayer-button.tsx @@ -91,7 +91,7 @@ const AddModal = ({ const onSubmit = (data: AddModalInput) => { const createRelayerData: CreateRelayerInput = { - chain: chainIdToChainRecord[data.chainId].slug, + chain: chainIdToChainRecord[data.chainId]?.slug ?? "unknown", backendWalletAddress: data.backendWalletAddress, name: data.name, allowedContracts: parseAddressListRaw(data.allowedContractsRaw), diff --git a/src/components/engine/relayer/relayers-table.tsx b/src/components/engine/relayer/relayers-table.tsx index 1dc2451a8a..f47c05b818 100644 --- a/src/components/engine/relayer/relayers-table.tsx +++ b/src/components/engine/relayer/relayers-table.tsx @@ -72,7 +72,7 @@ export const RelayersTable: React.FC = ({ return ( - {chain.name} + {chain?.name ?? "N/A"} ); }, @@ -227,7 +227,7 @@ const EditModal = ({ const onSubmit = (data: AddModalInput) => { const updateRelayerData: UpdateRelayerInput = { id: relayer.id, - chain: chainIdToChainRecord[data.chainId].slug, + chain: chainIdToChainRecord[data.chainId]?.slug ?? "unknown", backendWalletAddress: data.backendWalletAddress, name: data.name, allowedContracts: parseAddressListRaw(data.allowedContractsRaw), @@ -388,11 +388,11 @@ const RemoveModal = ({ - {chainIdToChainRecord[parseInt(relayer.chainId)].name} + {chainIdToChainRecord[parseInt(relayer.chainId)]?.name} From 9fcd00ae084e13609cf5877b96b7b5dbbd754c70 Mon Sep 17 00:00:00 2001 From: joaquim-verges <3353417+joaquim-verges@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:01:02 +0000 Subject: [PATCH 09/10] Update thirdweb packages to latest versions (#2664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR updates dependencies in the project, including `@thirdweb-dev/react`, `@thirdweb-dev/react-core`, and `@thirdweb-dev/sdk`. ### Detailed summary - Updated `@thirdweb-dev/react` from version 4.7.0 to 4.8.0 - Updated `@thirdweb-dev/react-core` from version 4.7.0 to 4.8.0 - Updated `@thirdweb-dev/sdk` from version 4.0.93 to 4.0.94 > The following files were skipped due to too many changes: `pnpm-lock.yaml` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- package.json | 6 +- pnpm-lock.yaml | 320 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 300 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 1750695406..ff0b32d7ef 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,9 @@ "@tanstack/react-query-persist-client": "^4.36.1", "@tanstack/react-table": "^8.17.3", "@thirdweb-dev/chains": "0.1.117", - "@thirdweb-dev/react": "4.7.0", - "@thirdweb-dev/react-core": "4.7.0", - "@thirdweb-dev/sdk": "4.0.93", + "@thirdweb-dev/react": "4.8.0", + "@thirdweb-dev/react-core": "4.8.0", + "@thirdweb-dev/sdk": "4.0.94", "@thirdweb-dev/service-utils": "0.4.31", "@thirdweb-dev/storage": "2.0.15", "@thirdweb-dev/wallets": "2.5.33", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e941169419..cea3b8d76a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,14 +86,14 @@ dependencies: specifier: 0.1.117 version: 0.1.117 '@thirdweb-dev/react': - specifier: 4.7.0 - version: 4.7.0(@babel/core@7.24.7)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@thirdweb-dev/sdk@4.0.93)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + specifier: 4.8.0 + version: 4.8.0(@babel/core@7.24.7)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@thirdweb-dev/sdk@4.0.94)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) '@thirdweb-dev/react-core': - specifier: 4.7.0 - version: 4.7.0(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + specifier: 4.8.0 + version: 4.8.0(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) '@thirdweb-dev/sdk': - specifier: 4.0.93 - version: 4.0.93(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + specifier: 4.0.94 + version: 4.0.94(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) '@thirdweb-dev/service-utils': specifier: 0.4.31 version: 0.4.31 @@ -5033,6 +5033,7 @@ packages: dependencies: is-glob: 4.0.3 micromatch: 4.0.7 + napi-wasm: 1.1.0 dev: false bundledDependencies: - napi-wasm @@ -7030,8 +7031,8 @@ packages: resolution: {integrity: sha512-046+AUSiDru/V9pajE1du8WayvBKeCvJ2NmKPy/mR8/SbKKrqmSbj7LJBfXE+nSq4f5TBXvnCzu0kcYebI9WdQ==} dev: false - /@thirdweb-dev/auth@4.1.91(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): - resolution: {integrity: sha512-+9FXC61CXUk7XJHgMF6pvoPcJha5tNN3Ynn3tHXnUw83mUh11UbS1T2NynKzHR/hlfdSmgxuoU6gG5QIu1YtVQ==} + /@thirdweb-dev/auth@4.1.92(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): + resolution: {integrity: sha512-+tC6lOzReezh516LiWHKSH6JA44Yz9QQ02F8htqgXZ/uxAYlBdoaZM69bfR9oiriiEibvPzdSVTMr6GHeHi1aw==} engines: {node: '>=18'} peerDependencies: cookie-parser: ^1.4.6 @@ -7055,7 +7056,7 @@ packages: optional: true dependencies: '@fastify/cookie': 9.3.1 - '@thirdweb-dev/wallets': 2.5.33(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/wallets': 2.5.34(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) cookie: 0.6.0 ethers: 5.7.2 fastify: 4.27.0 @@ -7115,6 +7116,11 @@ packages: engines: {node: '>=18'} dev: false + /@thirdweb-dev/chains@0.1.118: + resolution: {integrity: sha512-5U8O8LZUmkGuol0s9X8ysjghm/JhwyDBEIgchsJ6GmA00OYEX4h1zlYgNVeNdwr+g+mSjmU4QDMI6b/IQAnZ7g==} + engines: {node: '>=18'} + dev: false + /@thirdweb-dev/contracts-js@1.3.23(ethers@5.7.2): resolution: {integrity: sha512-AC8VbiYCZlWhiJl+uzScvbPznZce0mMzVwAZdBFZcfX7QE1kpDssocWna70ksmfCFkWLOrZuzTLYUoLatvOiBQ==} peerDependencies: @@ -7170,20 +7176,20 @@ packages: - utf-8-validate dev: false - /@thirdweb-dev/react-core@4.7.0(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): - resolution: {integrity: sha512-vQ9G7u/d5n9/I1M3u0fxwrjjGtYjQD99ggL46H30RX40v4zKAXkAsNdimiKz8ll2Rx0ROtn6cGLSLDIvLWWrow==} + /@thirdweb-dev/react-core@4.8.0(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): + resolution: {integrity: sha512-dTeX62USxA2uKNRyJAkZ9gKbv3oBHbuoEGFh4qCyGInFHY6qb2/dvnCpEP8kmyjgEGbF7xrhuZI3EuJUHF5X4Q==} engines: {node: '>=18'} peerDependencies: ethers: '>=5.5.1' react: '>=18.0.0' dependencies: '@tanstack/react-query': 4.36.1(react-dom@18.3.1)(react@18.3.1) - '@thirdweb-dev/auth': 4.1.91(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) - '@thirdweb-dev/chains': 0.1.117 + '@thirdweb-dev/auth': 4.1.92(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/chains': 0.1.118 '@thirdweb-dev/generated-abis': 0.0.2 - '@thirdweb-dev/sdk': 4.0.93(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/sdk': 4.0.94(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) '@thirdweb-dev/storage': 2.0.15 - '@thirdweb-dev/wallets': 2.5.33(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/wallets': 2.5.34(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) ethers: 5.7.2 mime: 3.0.0 react: 18.3.1 @@ -7239,11 +7245,11 @@ packages: - zksync-ethers dev: false - /@thirdweb-dev/react@4.7.0(@babel/core@7.24.7)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@thirdweb-dev/sdk@4.0.93)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): - resolution: {integrity: sha512-x2D4w6OYhczLTWgxfjxu8+N64puIurns4yNHsh19lzcHj7VlthzuWjcmULGJ2x6F0I/ELnKELBqgdwd3Cu4FRg==} + /@thirdweb-dev/react@4.8.0(@babel/core@7.24.7)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@thirdweb-dev/sdk@4.0.94)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): + resolution: {integrity: sha512-nbqUuVH8h9eOPjg6w/81ClCaiRi3rPObIJXWdmVsp+YDvm7S42SiNhooOZ8raLg3C++VOsT1kKa//O9f9/d1kw==} engines: {node: '>=18'} peerDependencies: - '@thirdweb-dev/sdk': ^4.0.93 + '@thirdweb-dev/sdk': ^4.0.94 ethers: '>=5.5.1' react: '>=18.0.0' react-dom: '>=18.0.0' @@ -7261,11 +7267,11 @@ packages: '@radix-ui/react-tabs': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-tooltip': 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@tanstack/react-query': 4.36.1(react-dom@18.3.1)(react@18.3.1) - '@thirdweb-dev/chains': 0.1.117 + '@thirdweb-dev/chains': 0.1.118 '@thirdweb-dev/payments': 1.0.5 - '@thirdweb-dev/react-core': 4.7.0(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) - '@thirdweb-dev/sdk': 4.0.93(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) - '@thirdweb-dev/wallets': 2.5.33(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/react-core': 4.8.0(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(fastify@4.27.0)(localforage@1.10.0)(next@14.2.4)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/sdk': 4.0.94(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@thirdweb-dev/wallets': 2.5.34(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) buffer: 6.0.3 copy-to-clipboard: 3.3.3 detect-browser: 5.3.0 @@ -7399,6 +7405,78 @@ packages: - utf-8-validate dev: false + /@thirdweb-dev/sdk@4.0.94(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): + resolution: {integrity: sha512-rSGZhkn++vCnLVp5/ic3tmru3mgTOQ7PnWf3L/mU45Bs2mpDPuv89vRHJOUT8TTYczMJX35DyX//yrHI5XC3NQ==} + engines: {node: '>=18'} + peerDependencies: + '@aws-sdk/client-secrets-manager': ^3.215.0 + ethers: ^5 + ethers-aws-kms-signer: ^1.3.2 + zksync-ethers: ^5.6.0 + peerDependenciesMeta: + '@aws-sdk/client-secrets-manager': + optional: true + ethers-aws-kms-signer: + optional: true + zksync-ethers: + optional: true + dependencies: + '@eth-optimism/sdk': 3.3.0(ethers@5.7.2) + '@thirdweb-dev/chains': 0.1.118 + '@thirdweb-dev/contracts-js': 1.3.23(ethers@5.7.2) + '@thirdweb-dev/crypto': 0.2.6 + '@thirdweb-dev/generated-abis': 0.0.2 + '@thirdweb-dev/merkletree': 0.2.6 + '@thirdweb-dev/storage': 2.0.15 + abitype: 1.0.0(typescript@5.4.5)(zod@3.23.8) + bn.js: 5.2.1 + bs58: 5.0.0 + buffer: 6.0.3 + ethers: 5.7.2 + eventemitter3: 5.0.1 + fast-deep-equal: 3.1.3 + thirdweb: 5.29.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zod@3.23.8) + tiny-invariant: 1.3.3 + tweetnacl: 1.0.3 + uuid: 9.0.1 + yaml: 2.4.5 + zksync-ethers: 5.8.0(ethers@5.7.2) + zod: 3.23.8 + transitivePeerDependencies: + - '@aws-sdk/client-lambda' + - '@aws-sdk/credential-providers' + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@coinbase/wallet-mobile-sdk' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@types/react-dom' + - '@upstash/redis' + - '@vercel/kv' + - amazon-cognito-identity-js + - aws-amplify + - bufferutil + - encoding + - expo-web-browser + - ioredis + - react + - react-dom + - react-native + - react-native-aes-gcm-crypto + - react-native-quick-crypto + - supports-color + - typescript + - uWebSockets.js + - utf-8-validate + dev: false + /@thirdweb-dev/service-utils@0.4.31: resolution: {integrity: sha512-naSbSmMesNaMjodz2F9DWVx+SKNAjwusrtrbj5EOVKrei0LrFOSenSpGIjmQUjNHNpBdaBcSnczWY3YI5XEETw==} dependencies: @@ -7514,6 +7592,104 @@ packages: - zksync-ethers dev: false + /@thirdweb-dev/wallets@2.5.34(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(localforage@1.10.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0): + resolution: {integrity: sha512-cTmspcaNsbBaymq4YhLe8uQ09z0nOVElDIXnkZm6s8d5mMq6xnN/EFnp23jmjBLq3Bsx8oGp3N5CLiCbXAz7YA==} + engines: {node: '>=18'} + peerDependencies: + '@aws-sdk/client-secrets-manager': ^3.256.0 + bs58: ^5.0.0 + ethers: ^5.7.2 + ethers-aws-kms-signer: ^1.3.2 + tweetnacl: ^1.0.3 + peerDependenciesMeta: + '@aws-sdk/client-secrets-manager': + optional: true + bs58: + optional: true + ethers: + optional: true + ethers-aws-kms-signer: + optional: true + tweetnacl: + optional: true + dependencies: + '@account-abstraction/contracts': 0.5.0 + '@blocto/sdk': 0.10.2 + '@coinbase/wallet-sdk': 3.9.3 + '@google-cloud/kms': 4.4.0 + '@magic-ext/connect': 6.7.2 + '@magic-ext/oauth': 7.6.2 + '@magic-sdk/provider': 13.6.2(localforage@1.10.0) + '@metamask/eth-sig-util': 4.0.1 + '@paperxyz/embedded-wallet-service-sdk': 1.2.5 + '@paperxyz/sdk-common-utilities': 0.1.1 + '@safe-global/safe-core-sdk': 3.3.5(ethers@5.7.2) + '@safe-global/safe-ethers-adapters': 0.1.0-alpha.19(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@ethersproject/bignumber@5.7.0)(@ethersproject/properties@5.7.0)(ethers@5.7.2) + '@safe-global/safe-ethers-lib': 1.9.4 + '@thirdweb-dev/chains': 0.1.118 + '@thirdweb-dev/contracts-js': 1.3.23(ethers@5.7.2) + '@thirdweb-dev/crypto': 0.2.6 + '@thirdweb-dev/sdk': 4.0.94(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zksync-ethers@5.8.0) + '@walletconnect/core': 2.13.2 + '@walletconnect/ethereum-provider': 2.12.2(@types/react@18.3.3)(react@18.3.1) + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/modal': 2.6.2(@types/react@18.3.3)(react@18.3.1) + '@walletconnect/types': 2.13.2 + '@walletconnect/utils': 2.13.2 + '@walletconnect/web3wallet': 1.12.2 + asn1.js: 5.4.1 + bn.js: 5.2.1 + buffer: 6.0.3 + eth-provider: 0.13.6 + ethereumjs-util: 7.1.5 + ethers: 5.7.2 + eventemitter3: 5.0.1 + key-encoder: 2.0.3 + magic-sdk: 13.6.2 + web3-core: 1.5.2 + transitivePeerDependencies: + - '@aws-sdk/client-lambda' + - '@aws-sdk/credential-providers' + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@coinbase/wallet-mobile-sdk' + - '@ethersproject/abstract-provider' + - '@ethersproject/abstract-signer' + - '@ethersproject/bignumber' + - '@ethersproject/properties' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@types/react-dom' + - '@upstash/redis' + - '@vercel/kv' + - amazon-cognito-identity-js + - aptos + - aws-amplify + - bufferutil + - debug + - encoding + - expo-web-browser + - ioredis + - localforage + - react + - react-dom + - react-native + - react-native-aes-gcm-crypto + - react-native-quick-crypto + - supports-color + - typescript + - uWebSockets.js + - utf-8-validate + - zksync-ethers + dev: false + /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -12017,7 +12193,6 @@ packages: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 dev: false - bundledDependencies: false /event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} @@ -15228,6 +15403,10 @@ packages: dev: false optional: true + /napi-wasm@1.1.0: + resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -18549,6 +18728,101 @@ packages: - zod dev: false + /thirdweb@5.29.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(ethers@5.7.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.4.5)(zod@3.23.8): + resolution: {integrity: sha512-V1fei/ZF+8Mr8Zf8WXSVWlPm1TOvrZnxzMF643gGYvlYO+GghNZs75/QEhUBR6+9lCWQ8YbuucNNSl5G+s7+7g==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@aws-sdk/client-lambda': ^3 + '@aws-sdk/credential-providers': ^3 + '@coinbase/wallet-mobile-sdk': ^1 + '@react-native-async-storage/async-storage': ^1 + amazon-cognito-identity-js: ^6 + aws-amplify: ^5 + ethers: ^5 || ^6 + expo-web-browser: ^13 + react: '>=18' + react-native: '>=0.70' + react-native-aes-gcm-crypto: ^0.2 + react-native-quick-crypto: '>=0.7.0-rc.6 || >=0.7' + typescript: '>=5.0.4' + peerDependenciesMeta: + '@aws-sdk/client-lambda': + optional: true + '@aws-sdk/credential-providers': + optional: true + '@coinbase/wallet-mobile-sdk': + optional: true + '@react-native-async-storage/async-storage': + optional: true + amazon-cognito-identity-js: + optional: true + aws-amplify: + optional: true + ethers: + optional: true + expo-web-browser: + optional: true + react: + optional: true + react-native: + optional: true + react-native-aes-gcm-crypto: + optional: true + react-native-quick-crypto: + optional: true + typescript: + optional: true + dependencies: + '@coinbase/wallet-sdk': 4.0.3 + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.3.3)(react@18.3.1) + '@google/model-viewer': 2.1.1 + '@noble/curves': 1.4.0 + '@noble/hashes': 1.4.0 + '@passwordless-id/webauthn': 1.6.1 + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-icons': 1.3.0(react@18.3.1) + '@radix-ui/react-tooltip': 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@tanstack/react-query': 5.29.2(react@18.3.1) + '@walletconnect/ethereum-provider': 2.12.2(@types/react@18.3.3)(react@18.3.1) + '@walletconnect/sign-client': 2.13.2 + abitype: 1.0.0(typescript@5.4.5)(zod@3.23.8) + ethers: 5.7.2 + fast-text-encoding: 1.0.6 + fuse.js: 7.0.0 + input-otp: 1.2.4(react-dom@18.3.1)(react@18.3.1) + mipd: 0.0.7(typescript@5.4.5) + node-libs-browser: 2.2.1 + react: 18.3.1 + typescript: 5.4.5 + uqr: 0.1.2 + viem: 2.13.7(typescript@5.4.5)(zod@3.23.8) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/react' + - '@types/react-dom' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react-dom + - supports-color + - uWebSockets.js + - utf-8-validate + - zod + dev: false + /thread-stream@0.15.2: resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} dependencies: From 4112dcf4cc0a144a1e43c4dd277565164ffb8b1c Mon Sep 17 00:00:00 2001 From: jnsdls Date: Fri, 14 Jun 2024 17:30:48 +0000 Subject: [PATCH 10/10] feat: add 'AddChainToWallet' button and integrate it into ChainPageLayout (#2666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created a new component 'AddChainToWallet' to facilitate adding chains to wallet. - Integrated 'AddChainToWallet' button into ChainPageLayout. - Updated layout.tsx to include 'AddChainToWallet' button and its behavior. - Refactored chain verification and metadata handling to accommodate the new feature. - Adjusted related utility functions in utils.ts. --- ## PR-Codex overview The focus of this PR is to update the chainlist components by introducing a new `isVerified` flag and refactoring the rendering logic based on it. ### Detailed summary - Introduces `isVerified` flag to determine if chain metadata is available - Updates rendering logic in `ChainListRow` and `ChainListCard` components based on `isVerified` - Adds `AddChainToWallet` component for connecting to chains - Adjusts layout and styling based on `isVerified` flag and chain metadata > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../components/client/add-chain-to-wallet.tsx | 56 +++++++++ .../(dashboard)/(chain)/[chain_id]/layout.tsx | 118 +++++++++++------- .../components/server/chainlist-card.tsx | 7 +- .../components/server/chainlist-row.tsx | 3 +- src/app/(dashboard)/(chain)/utils.ts | 5 +- 5 files changed, 134 insertions(+), 55 deletions(-) create mode 100644 src/app/(dashboard)/(chain)/[chain_id]/components/client/add-chain-to-wallet.tsx diff --git a/src/app/(dashboard)/(chain)/[chain_id]/components/client/add-chain-to-wallet.tsx b/src/app/(dashboard)/(chain)/[chain_id]/components/client/add-chain-to-wallet.tsx new file mode 100644 index 0000000000..dc96f6f341 --- /dev/null +++ b/src/app/(dashboard)/(chain)/[chain_id]/components/client/add-chain-to-wallet.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { useAddress, useChainId, useSwitchChain } from "@thirdweb-dev/react"; +import { ToolTipLabel } from "@/components/ui/tooltip"; +import { useMutation } from "@tanstack/react-query"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { useDebounce } from "use-debounce"; + +type AddChainToWalletProps = { + chainId: number; +}; + +export const AddChainToWallet: React.FC = (props) => { + const address = useAddress(); + const switchChain = useSwitchChain(); + const activeWalletChainId = useChainId(); + + const switchChainMutation = useMutation({ + mutationFn: async () => { + await switchChain(props.chainId); + }, + }); + + // debounce the loading state to prevent flickering + const [debouncedLoading] = useDebounce(switchChainMutation.isLoading, 50); + + const buttonContent = ( + + ); + + if (address && activeWalletChainId !== props.chainId) { + return buttonContent; + } + + return ( + +
{buttonContent}
+
+ ); +}; diff --git a/src/app/(dashboard)/(chain)/[chain_id]/layout.tsx b/src/app/(dashboard)/(chain)/[chain_id]/layout.tsx index b754caae18..bd11ab9900 100644 --- a/src/app/(dashboard)/(chain)/[chain_id]/layout.tsx +++ b/src/app/(dashboard)/(chain)/[chain_id]/layout.tsx @@ -21,6 +21,8 @@ import { ChainIcon } from "../components/server/chain-icon"; import { Badge } from "@/components/ui/badge"; import { getChain, getChainMetadata } from "../utils"; import { ChainCTA } from "./components/server/cta-card"; +import { Button } from "@/components/ui/button"; +import { AddChainToWallet } from "./components/client/add-chain-to-wallet"; export async function generateMetadata( { params }: { params: { chain_id: string } }, @@ -66,6 +68,8 @@ export default async function ChainPageLayout({ redirect(chain.slug); } const chainMetadata = await getChainMetadata(chain.chainId); + // true if we *have* chainMetadata for the chain + const isVerified = !!chainMetadata; const isDeprecated = chain.status === "deprecated"; return ( @@ -89,9 +93,7 @@ export default async function ChainPageLayout({
@@ -111,56 +113,78 @@ export default async function ChainPageLayout({
{/* end header shaningans */} +
+
+ + + Chainlist + -
- - - Chainlist - - -
- {chain.icon?.url && ( - - )} +
+ {chain.icon?.url && ( + + )} - {/* Chain Name */} + {/* Chain Name */} + +

+ {chain.name} +

+ +
-

+ {isVerified && ( + + + + verified + + )} - > - {chain.name} -

- + {chainMetadata?.gasSponsored && ( + + + + gas sponsored + + + )} +
- -
- {chainMetadata?.verified && ( - - - verified - - )} - {chainMetadata?.gasSponsored && ( - + + {isVerified ? null : ( + )}
diff --git a/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-card.tsx b/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-card.tsx index f37804f5be..7cd96c63a3 100644 --- a/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-card.tsx +++ b/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-card.tsx @@ -27,6 +27,7 @@ export async function ChainListCard({ iconUrl, }: ChainListCardProps) { const chainMetadata = await getChainMetadata(chainId); + const isVerified = !!chainMetadata; return (
@@ -70,13 +71,11 @@ export async function ChainListCard({ - {(isDeprecated || - chainMetadata?.gasSponsored || - chainMetadata?.verified) && ( + {(isDeprecated || chainMetadata?.gasSponsored || isVerified) && (
{!isDeprecated && ( <> - {chainMetadata?.verified && ( + {isVerified && (

Verified

diff --git a/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-row.tsx b/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-row.tsx index 86f9f12ef0..2bf83783e4 100644 --- a/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-row.tsx +++ b/src/app/(dashboard)/(chain)/chainlist/components/server/chainlist-row.tsx @@ -35,6 +35,7 @@ export async function ChainListRow({ iconUrl, }: ChainListRowProps) { const chainMetadata = await getChainMetadata(chainId); + const isVerified = !!chainMetadata; return ( {favoriteButton} @@ -50,7 +51,7 @@ export async function ChainListRow({ {chainName} - {chainMetadata?.verified && ( + {isVerified && ( diff --git a/src/app/(dashboard)/(chain)/utils.ts b/src/app/(dashboard)/(chain)/utils.ts index d7a27051f4..40f90f45df 100644 --- a/src/app/(dashboard)/(chain)/utils.ts +++ b/src/app/(dashboard)/(chain)/utils.ts @@ -45,7 +45,6 @@ type ChainMetadata = Partial<{ headerImgUrl: string; about: string; gasSponsored: boolean; - verified: boolean; cta: ChainCTAProps; }>; @@ -66,7 +65,7 @@ const chainMetaRecord = { headerImgUrl: xaiBanner.src, about: "Xai was developed to enable real economies and open trade in the next generation of video games. With Xai, potentially billions of traditional gamers can own and trade valuable in-game items in their favorite games for the first time, without the need to use crypto-wallets.", - verified: true, + cta: { title: "Unlock ultimate possibility with Xai Connect", backgroundImageUrl: xaiCTABg.src, @@ -80,7 +79,7 @@ const chainMetaRecord = { about: "Base is a secure, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain.", gasSponsored: true, - verified: true, + cta: OP_CTA, }, // optimism