From ceb87dc60f83d1af523a1457cf33ebfd6fa35a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Wed, 31 Jul 2024 20:32:04 -0400 Subject: [PATCH] ECO-258: Log error to DD (#769) * Log error to DD in prod * log error * add context --- apps/web/app/(basenames)/layout.tsx | 11 +-- .../app/(basenames)/name/[username]/page.tsx | 13 ++-- apps/web/app/(basenames)/names/page.tsx | 13 ++-- apps/web/app/AppProviders.tsx | 70 ++++++++++--------- apps/web/app/datadog.ts | 2 +- apps/web/contexts/Errors.tsx | 57 +++++++++++++++ .../basenames/[name]/assets/cardImage.svg.tsx | 3 +- .../[name]/assets/coverImage.png.tsx | 2 +- .../pages/api/basenames/contract-uri.json.ts | 2 +- apps/web/pages/api/basenames/contract-uri.ts | 3 +- .../pages/api/basenames/metadata/[tokenId].ts | 3 +- .../Basenames/RegistrationContext.tsx | 14 +++- .../Basenames/RegistrationForm/index.tsx | 8 ++- .../RegistrationProfileForm/index.tsx | 12 +++- .../UsernameProfileEditModal/index.tsx | 22 +++--- apps/web/src/components/Jobs/JobsList.tsx | 8 ++- .../components/Layout/UsernameNav/index.tsx | 2 +- .../hooks/useAlternativeNameSuggestions.ts | 7 +- apps/web/src/hooks/useAttestations.ts | 45 +++++++----- apps/web/src/hooks/useBasenameChain.ts | 4 +- apps/web/src/hooks/useRegisterNameCallback.ts | 5 +- 21 files changed, 214 insertions(+), 92 deletions(-) create mode 100644 apps/web/contexts/Errors.tsx diff --git a/apps/web/app/(basenames)/layout.tsx b/apps/web/app/(basenames)/layout.tsx index 2118194..493d746 100644 --- a/apps/web/app/(basenames)/layout.tsx +++ b/apps/web/app/(basenames)/layout.tsx @@ -1,3 +1,4 @@ +import ErrorsProvider from 'apps/web/contexts/Errors'; import UsernameNav from 'apps/web/src/components/Layout/UsernameNav'; import type { Metadata } from 'next'; @@ -25,9 +26,11 @@ export default async function BasenameLayout({ children: React.ReactNode; }) { return ( -
- - {children} -
+ +
+ + {children} +
+
); } diff --git a/apps/web/app/(basenames)/name/[username]/page.tsx b/apps/web/app/(basenames)/name/[username]/page.tsx index 88982dd..be97062 100644 --- a/apps/web/app/(basenames)/name/[username]/page.tsx +++ b/apps/web/app/(basenames)/name/[username]/page.tsx @@ -9,6 +9,7 @@ import { redirect } from 'next/navigation'; import classNames from 'classnames'; import { BaseName } from '@coinbase/onchainkit/identity'; import UsernameProfile from 'apps/web/src/components/Basenames/UsernameProfile'; +import ErrorsProvider from 'apps/web/contexts/Errors'; type UsernameProfileProps = { params: { username: BaseName }; @@ -49,10 +50,12 @@ export default async function Username({ params }: UsernameProfileProps) { ); return ( - -
- -
-
+ + +
+ +
+
+
); } diff --git a/apps/web/app/(basenames)/names/page.tsx b/apps/web/app/(basenames)/names/page.tsx index ccbc4a3..8206e9b 100644 --- a/apps/web/app/(basenames)/names/page.tsx +++ b/apps/web/app/(basenames)/names/page.tsx @@ -1,4 +1,5 @@ import RegistrationProviders from 'apps/web/app/(basenames)/names/RegistrationProviders'; +import ErrorsProvider from 'apps/web/contexts/Errors'; import RegistrationFlow from 'apps/web/src/components/Basenames/RegistrationFlow'; import type { Metadata } from 'next'; import { Suspense } from 'react'; @@ -16,10 +17,12 @@ export const metadata: Metadata = { export default async function Page() { return ( - - - - - + + + + + + + ); } diff --git a/apps/web/app/AppProviders.tsx b/apps/web/app/AppProviders.tsx index fc77c16..1d3e82c 100644 --- a/apps/web/app/AppProviders.tsx +++ b/apps/web/app/AppProviders.tsx @@ -27,6 +27,8 @@ import { base, baseSepolia } from 'wagmi/chains'; import { cookieManagerConfig } from '../src/utils/cookieManagerConfig'; import ClientAnalyticsScript from 'apps/web/src/components/ClientAnalyticsScript/ClientAnalyticsScript'; import dynamic from 'next/dynamic'; +import ErrorsProvider from 'apps/web/contexts/Errors'; +import { isDevelopment } from 'apps/web/src/constants'; const DynamicCookieBannerWrapper = dynamic( async () => import('apps/web/src/components/CookieBannerWrapper'), @@ -102,42 +104,42 @@ export default function AppProviders({ children }: AppProvidersProps) { const handleLogError = useCallback((err: Error) => console.error(err), []); - const isDevelopment = process.env.NODE_ENV === 'development'; - useSprig(sprigEnvironmentId); return ( - - - - - - - - - - <> - {children} - - - - - - - - - - + + + + + + + + + + + <> + {children} + + + + + + + + + + + ); } diff --git a/apps/web/app/datadog.ts b/apps/web/app/datadog.ts index b6cc611..fc71dfd 100644 --- a/apps/web/app/datadog.ts +++ b/apps/web/app/datadog.ts @@ -2,11 +2,11 @@ 'use client'; import { datadogRum } from '@datadog/browser-rum'; +import { isDevelopment } from 'apps/web/src/constants'; import { useEffect } from 'react'; const nextPublicDatadogAppId = process.env.NEXT_PUBLIC_DATADOG_APP_ID ?? ''; const nextPublicDatadogClientToken = process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN ?? ''; -const isDevelopment = process.env.NODE_ENV === 'development'; export default function DatadogInit() { useEffect(() => { diff --git a/apps/web/contexts/Errors.tsx b/apps/web/contexts/Errors.tsx new file mode 100644 index 0000000..3b9c7e7 --- /dev/null +++ b/apps/web/contexts/Errors.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { datadogRum } from '@datadog/browser-rum'; +import { isDevelopment } from 'apps/web/src/constants'; +import { ReactNode, createContext, useCallback, useContext, useMemo } from 'react'; + +export type ErrorsContextProps = { + logError: (error: unknown, message: string) => void; + fullContext: string; +}; + +export const ErrorsContext = createContext({ + logError: function () { + return undefined; + }, + fullContext: '', +}); + +export function useErrors() { + const context = useContext(ErrorsContext); + if (context === undefined) { + throw new Error('useErrors must be used within a ErrorsProvider'); + } + return context; +} + +type ErrorsProviderProps = { + children?: ReactNode; + context: string; +}; + +export default function ErrorsProvider({ children, context }: ErrorsProviderProps) { + const { fullContext: previousContext } = useErrors(); + + const fullContext = [previousContext, context].filter((c) => !!c).join('_'); + + const logError = useCallback( + (error: unknown, message: string) => { + if (isDevelopment) { + console.log('\n--------------------------------------'); + console.info(`Error caught with message: "${message}"`); + console.error(error); + console.info(`Context: "${fullContext}"`); + console.log('--------------------------------------\n'); + return; + } + datadogRum.addError(error, { context: fullContext, message: message }); + }, + [fullContext], + ); + + const values = useMemo(() => { + return { logError, context, fullContext }; + }, [context, fullContext, logError]); + + return {children}; +} diff --git a/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx b/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx index c700acc..0c83724 100644 --- a/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx +++ b/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx @@ -7,6 +7,7 @@ import { namehash } from 'viem'; import { getBasenamePublicClient } from 'apps/web/src/hooks/useBasenameChain'; import L2ResolverAbi from 'apps/web/src/abis/L2Resolver'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; +import { isDevelopment } from 'apps/web/src/constants'; const emojiCache: Record> = {}; @@ -33,7 +34,7 @@ export default async function handler(request: NextRequest) { ).then(async (res) => res.arrayBuffer()); const url = new URL(request.url); - const isDevelopment = process.env.NODE_ENV === 'development'; + const username = url.searchParams.get('name') ?? 'yourname'; const domainName = isDevelopment ? `${url.protocol}//${url.host}` : 'https://www.base.org'; const profilePicture = getUserNamePicture(username); diff --git a/apps/web/pages/api/basenames/[name]/assets/coverImage.png.tsx b/apps/web/pages/api/basenames/[name]/assets/coverImage.png.tsx index 135908e..bfb45c0 100644 --- a/apps/web/pages/api/basenames/[name]/assets/coverImage.png.tsx +++ b/apps/web/pages/api/basenames/[name]/assets/coverImage.png.tsx @@ -8,6 +8,7 @@ import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames import L2ResolverAbi from 'apps/web/src/abis/L2Resolver'; import { base } from 'viem/chains'; import { getBasenamePublicClient } from 'apps/web/src/hooks/useBasenameChain'; +import { isDevelopment } from 'apps/web/src/constants'; export const config = { runtime: 'edge', @@ -19,7 +20,6 @@ export default async function handler(request: NextRequest) { ).then(async (res) => res.arrayBuffer()); const url = new URL(request.url); - const isDevelopment = process.env.NODE_ENV === 'development'; const username = url.searchParams.get('name') ?? 'yourname'; const domainName = isDevelopment ? `${url.protocol}//${url.host}` : 'https://www.base.org'; const profilePicture = getUserNamePicture(username); diff --git a/apps/web/pages/api/basenames/contract-uri.json.ts b/apps/web/pages/api/basenames/contract-uri.json.ts index f2b46c5..c16e0a1 100644 --- a/apps/web/pages/api/basenames/contract-uri.json.ts +++ b/apps/web/pages/api/basenames/contract-uri.json.ts @@ -1,3 +1,4 @@ +import { isDevelopment } from 'apps/web/src/constants'; import { NextResponse } from 'next/server'; import { base } from 'viem/chains'; @@ -7,7 +8,6 @@ export const config = { export default async function GET(request: Request) { const url = new URL(request.url); - const isDevelopment = process.env.NODE_ENV === 'development'; const domainName = isDevelopment ? `${url.protocol}//${url.host}` : 'https://www.base.org'; const chainId = url.searchParams.get('chainId') ?? base.id; diff --git a/apps/web/pages/api/basenames/contract-uri.ts b/apps/web/pages/api/basenames/contract-uri.ts index adc7b02..bb1a500 100644 --- a/apps/web/pages/api/basenames/contract-uri.ts +++ b/apps/web/pages/api/basenames/contract-uri.ts @@ -1,3 +1,4 @@ +import { isDevelopment } from 'apps/web/src/constants'; import { NextResponse } from 'next/server'; import { base } from 'viem/chains'; @@ -7,7 +8,7 @@ export const config = { export default async function GET(request: Request) { const url = new URL(request.url); - const isDevelopment = process.env.NODE_ENV === 'development'; + const domainName = isDevelopment ? `${url.protocol}//${url.host}` : 'https://www.base.org'; const chainId = url.searchParams.get('chainId') ?? base.id; if (!chainId) return NextResponse.json({ error: '406: chainId is missing' }, { status: 406 }); diff --git a/apps/web/pages/api/basenames/metadata/[tokenId].ts b/apps/web/pages/api/basenames/metadata/[tokenId].ts index b8f38d4..498d80f 100644 --- a/apps/web/pages/api/basenames/metadata/[tokenId].ts +++ b/apps/web/pages/api/basenames/metadata/[tokenId].ts @@ -1,6 +1,7 @@ import { premintMapping } from 'apps/web/pages/api/basenames/metadata/premintsMapping'; import L2Resolver from 'apps/web/src/abis/L2Resolver'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; +import { isDevelopment } from 'apps/web/src/constants'; import { getBasenamePublicClient } from 'apps/web/src/hooks/useBasenameChain'; import { USERNAME_DOMAINS } from 'apps/web/src/utils/usernames'; import { NextResponse } from 'next/server'; @@ -13,7 +14,7 @@ export const config = { export default async function GET(request: Request) { const url = new URL(request.url); - const isDevelopment = process.env.NODE_ENV === 'development'; + const domainName = isDevelopment ? `${url.protocol}//${url.host}` : 'https://www.base.org'; let tokenId = url.searchParams.get('tokenId'); if (tokenId?.endsWith('.json')) tokenId = tokenId.slice(0, -5); diff --git a/apps/web/src/components/Basenames/RegistrationContext.tsx b/apps/web/src/components/Basenames/RegistrationContext.tsx index 946769b..a6258ae 100644 --- a/apps/web/src/components/Basenames/RegistrationContext.tsx +++ b/apps/web/src/components/Basenames/RegistrationContext.tsx @@ -1,5 +1,6 @@ 'use client'; import { useAnalytics } from 'apps/web/contexts/Analytics'; +import { useErrors } from 'apps/web/contexts/Errors'; import { DiscountData, findFirstValidDiscount, @@ -113,6 +114,7 @@ export default function RegistrationProvider({ children }: RegistrationProviderP // Analytics const { logEventWithContext } = useAnalytics(); + const { logError } = useErrors(); // Web3 data const { address } = useAccount(); @@ -152,6 +154,7 @@ export default function RegistrationProvider({ children }: RegistrationProviderP enabled: !!registerNameTransactionHash, }, }); + const [registerNameCallsBatchId, setRegisterNameCallsBatchId] = useState(''); // The "correct" way to transition the UI would be to watch for call success, but this experimental @@ -174,7 +177,9 @@ export default function RegistrationProvider({ children }: RegistrationProviderP setRegistrationStep(RegistrationSteps.Success); } }) - .catch(() => {}); + .catch((error) => { + logError(error, 'Failed to refetch basename'); + }); }, 1500); const redirectToProfile = useCallback(() => { @@ -232,6 +237,13 @@ export default function RegistrationProvider({ children }: RegistrationProviderP logEventWithContext('selected_name', ActionType.change); }, [logEventWithContext, selectedName]); + // Log error + useEffect(() => { + if (transactionError) { + logError(transactionError, 'Failed to fetch the transaction receipt'); + } + }, [logError, transactionError]); + const values = useMemo(() => { return { searchInputFocused, diff --git a/apps/web/src/components/Basenames/RegistrationForm/index.tsx b/apps/web/src/components/Basenames/RegistrationForm/index.tsx index 3565cba..3bf4ee9 100644 --- a/apps/web/src/components/Basenames/RegistrationForm/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationForm/index.tsx @@ -6,6 +6,7 @@ import { } from '@heroicons/react/16/solid'; import { ConnectButton, useConnectModal } from '@rainbow-me/rainbowkit'; import { useAnalytics } from 'apps/web/contexts/Analytics'; +import { useErrors } from 'apps/web/contexts/Errors'; import { useRegistration } from 'apps/web/src/components/Basenames/RegistrationContext'; import RegistrationLearnMoreModal from 'apps/web/src/components/Basenames/RegistrationLearnMoreModal'; import { Button, ButtonSizes, ButtonVariants } from 'apps/web/src/components/Button/Button'; @@ -50,6 +51,7 @@ export default function RegistrationForm() { const chains = useChains(); const { openConnectModal } = useConnectModal(); const { logEventWithContext } = useAnalytics(); + const { logError } = useErrors(); const { basenameChain } = useBasenameChain(); const { switchChain } = useSwitchChain(); const switchToIntendedNetwork = useCallback( @@ -132,8 +134,10 @@ export default function RegistrationForm() { const registerNameCallback = useCallback(() => { registerName() .then(() => {}) - .catch(() => {}); - }, [registerName]); + .catch((error) => { + logError(error, 'Failed to register name'); + }); + }, [logError, registerName]); const { data: balance } = useBalance({ address, chainId: connectedChain?.id }); const insufficientBalanceToRegister = diff --git a/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx b/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx index 87e1fe2..c4fc8e1 100644 --- a/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx @@ -1,4 +1,5 @@ import { useAnalytics } from 'apps/web/contexts/Analytics'; +import { useErrors } from 'apps/web/contexts/Errors'; import { registrationTransitionDuration, useRegistration, @@ -35,6 +36,7 @@ export enum FormSteps { export default function RegistrationProfileForm() { const [currentFormStep, setCurrentFormStep] = useState(FormSteps.Description); const [transitionStep, setTransitionStep] = useState(false); + const { logError } = useErrors(); const { selectedName, redirectToProfile } = useRegistration(); const { address } = useAccount(); const { logEventWithContext } = useAnalytics(); @@ -90,7 +92,9 @@ export default function RegistrationProfileForm() { .then(() => { redirectToProfile(); }) - .catch(() => {}); + .catch((error) => { + logError(error, 'Failed to refetch text records'); + }); } if (transactionData.status === 'reverted') { @@ -106,6 +110,7 @@ export default function RegistrationProfileForm() { selectedName, basenameChain.id, redirectToProfile, + logError, ]); useEffect(() => { @@ -165,13 +170,16 @@ export default function RegistrationProfileForm() { redirectToProfile(); } }) - .catch(console.error); + .catch((error) => { + logError(error, 'Failed to write text records'); + }); } event.preventDefault(); }, [ currentFormStep, + logError, logEventWithContext, redirectToProfile, textRecords, diff --git a/apps/web/src/components/Basenames/UsernameProfileEditModal/index.tsx b/apps/web/src/components/Basenames/UsernameProfileEditModal/index.tsx index 97173f2..2143387 100644 --- a/apps/web/src/components/Basenames/UsernameProfileEditModal/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileEditModal/index.tsx @@ -1,5 +1,6 @@ import { upload } from '@vercel/blob/client'; import { useAnalytics } from 'apps/web/contexts/Analytics'; +import { useErrors } from 'apps/web/contexts/Errors'; import UsernameAvatarField from 'apps/web/src/components/Basenames/UsernameAvatarField'; import UsernameDescriptionField from 'apps/web/src/components/Basenames/UsernameDescriptionField'; import UsernameKeywordsField from 'apps/web/src/components/Basenames/UsernameKeywordsField'; @@ -35,6 +36,7 @@ export default function UsernameProfileEditModal({ const { profileUsername, profileAddress, currentWalletIsOwner } = useUsernameProfile(); const [avatarFile, setAvatarFile] = useState(); const { logEventWithContext } = useAnalytics(); + const { logError } = useErrors(); const { basenameChain } = useBasenameChain(profileUsername); const { @@ -88,7 +90,9 @@ export default function UsernameProfileEditModal({ .then(() => { toggleModal(); }) - .catch(() => {}); + .catch((error) => { + logError(error, 'Failed to refetch existing text records'); + }); } if (transactionData.status === 'reverted') { @@ -103,6 +107,7 @@ export default function UsernameProfileEditModal({ logEventWithContext, transactionIsFetching, toggleModal, + logError, ]); useEffect(() => { @@ -174,25 +179,26 @@ export default function UsernameProfileEditModal({ }) .catch((error) => { - console.error(error); + logError(error, 'Update text records transaction canceled'); logEventWithContext('update_text_records_transaction_canceled', ActionType.click); }); }) - .catch((e) => { - console.error(e); + .catch((error) => { + logError(error, 'Failed to upload avatar'); logEventWithContext('avatar_upload_failed', ActionType.error); }); logEventWithContext('update_text_records_transaction_initiated', ActionType.change); }, [ - avatarFile, currentWalletIsOwner, - logEventWithContext, - uploadAvatar, textRecords, - toggleModal, + uploadAvatar, + avatarFile, + logEventWithContext, writeTextRecords, + toggleModal, + logError, ], ); diff --git a/apps/web/src/components/Jobs/JobsList.tsx b/apps/web/src/components/Jobs/JobsList.tsx index 4476f82..44b5fcd 100644 --- a/apps/web/src/components/Jobs/JobsList.tsx +++ b/apps/web/src/components/Jobs/JobsList.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from 'react'; import { Divider } from 'apps/web/src/components/Divider/Divider'; import { Job } from 'apps/web/src/components/Jobs/Job'; import { greenhouseApiUrl } from 'apps/web/src/constants'; +import { useErrors } from 'apps/web/contexts/Errors'; async function getJobs() { const res = await fetch(`${greenhouseApiUrl}/boards/basejobs/jobs?content=true`); @@ -30,11 +31,14 @@ export type JobType = { export default function JobsList() { const [jobs, setJobs] = useState([]); + const { logError } = useErrors(); useEffect(() => { getJobs() .then((js) => setJobs(js)) - .catch(console.error); - }, []); + .catch((error) => { + logError(error, 'Failed to get jobs'); + }); + }, [logError]); const departments = useMemo(() => { const departmentsById = jobs.reduce((acc, job) => { diff --git a/apps/web/src/components/Layout/UsernameNav/index.tsx b/apps/web/src/components/Layout/UsernameNav/index.tsx index 9493129..c4c4afc 100644 --- a/apps/web/src/components/Layout/UsernameNav/index.tsx +++ b/apps/web/src/components/Layout/UsernameNav/index.tsx @@ -13,10 +13,10 @@ import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; import { base, baseSepolia } from 'viem/chains'; import { Icon } from 'apps/web/src/components/Icon/Icon'; import { useCallback } from 'react'; +import { isDevelopment } from 'apps/web/src/constants'; export default function UsernameNav() { const { isConnected } = useAccount(); - const isDevelopment = process.env.NODE_ENV === 'development'; const { basenameChain } = useBasenameChain(); const { switchChain } = useSwitchChain(); diff --git a/apps/web/src/hooks/useAlternativeNameSuggestions.ts b/apps/web/src/hooks/useAlternativeNameSuggestions.ts index 477a60d..34c0ac3 100644 --- a/apps/web/src/hooks/useAlternativeNameSuggestions.ts +++ b/apps/web/src/hooks/useAlternativeNameSuggestions.ts @@ -1,3 +1,4 @@ +import { useErrors } from 'apps/web/contexts/Errors'; import { NameSuggestionResponseData } from 'apps/web/pages/api/name/[alreadyClaimedName]'; import { useAreNamesAvailable } from 'apps/web/src/hooks/useIsNameAvailable'; import { normalizeEnsDomainName, validateEnsDomainName } from 'apps/web/src/utils/usernames'; @@ -7,7 +8,7 @@ export function useAlternativeNameSuggestions(nameNeedingAlternatives: string, d const [suggestions, setSuggestions] = useState(); const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); - + const { logError } = useErrors(); useEffect(() => { async function checkAlternatives() { if (!doLookup || !nameNeedingAlternatives || nameNeedingAlternatives.length < 3) { @@ -21,14 +22,14 @@ export function useAlternativeNameSuggestions(nameNeedingAlternatives: string, d setSuggestions(suggestionData.suggestion); } } catch (e) { - console.error('error checking for alternative names: ', e); + logError(e, 'Failed to fetch alternative names'); setError('error fetching name suggestions'); } finally { setIsLoading(false); } } void checkAlternatives(); - }, [doLookup, nameNeedingAlternatives]); + }, [doLookup, logError, nameNeedingAlternatives]); const normalizedNames = useMemo( () => diff --git a/apps/web/src/hooks/useAttestations.ts b/apps/web/src/hooks/useAttestations.ts index 71d51d0..5d094f9 100644 --- a/apps/web/src/hooks/useAttestations.ts +++ b/apps/web/src/hooks/useAttestations.ts @@ -9,6 +9,7 @@ import { useEffect, useMemo, useState } from 'react'; import { Address, ReadContractErrorType, encodeAbiParameters } from 'viem'; import { useAccount, useReadContract } from 'wagmi'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; +import { useErrors } from 'apps/web/contexts/Errors'; export type AttestationData = { discountValidatorAddress: Address; @@ -21,6 +22,7 @@ type AttestationHookReturns = { error: ReadContractErrorType | null; }; export function useCheckCBIDAttestations(): AttestationHookReturns { + const { logError } = useErrors(); const { address } = useAccount(); const [cBIDProofResponse, setCBIDProofResponse] = useState(null); const { basenameChain } = useBasenameChain(); @@ -35,15 +37,17 @@ export function useCheckCBIDAttestations(): AttestationHookReturns { const result = (await response.json()) as CBIDProofResponse; setCBIDProofResponse(result); } - } catch (e) { - console.error('Error checking CB.ID attestation:', e); + } catch (error) { + logError(error, 'Error checking CB.ID attestation'); } } if (address && !IS_EARLY_ACCESS) { - checkCBIDAttestations(address).catch(console.error); + checkCBIDAttestations(address).catch((error) => { + logError(error, 'Error checking CB.ID attestation'); + }); } - }, [address, basenameChain.id]); + }, [address, basenameChain.id, logError]); const encodedProof = useMemo( () => @@ -87,6 +91,7 @@ export function useCheckCBIDAttestations(): AttestationHookReturns { // returns info about Coinbase verified account attestations export function useCheckCoinbaseAttestations() { + const { logError } = useErrors(); const { address } = useAccount(); const [loading, setLoading] = useState(false); const [coinbaseProofResponse, setCoinbaseProofResponse] = useState( @@ -106,17 +111,19 @@ export function useCheckCoinbaseAttestations() { if (response.ok) { setCoinbaseProofResponse(result); } - } catch (e) { - console.error('Error checking Coinbase account attestations:', e); + } catch (error) { + logError(error, 'Error checking Coinbase account attestations'); } finally { setLoading(false); } } if (address && !IS_EARLY_ACCESS) { - checkCoinbaseAttestations(address).catch(console.error); + checkCoinbaseAttestations(address).catch((error) => { + logError(error, 'Error checking Coinbase account attestations'); + }); } - }, [address, basenameChain.id]); + }, [address, basenameChain.id, logError]); const signature = coinbaseProofResponse?.signedMessage as undefined | `0x${string}`; @@ -149,6 +156,7 @@ export function useCheckCoinbaseAttestations() { } export function useCheckCB1Attestations() { + const { logError } = useErrors(); const { address } = useAccount(); const [loading, setLoading] = useState(false); const [cb1ProofResponse, setCB1ProofResponse] = useState(null); @@ -165,17 +173,19 @@ export function useCheckCB1Attestations() { const result = (await response.json()) as CoinbaseProofResponse; setCB1ProofResponse(result); } - } catch (e) { - console.error('Error checking CB1 attestation:', e); + } catch (error) { + logError(error, 'Error checking CB1 attestation'); } finally { setLoading(false); } } if (address && !IS_EARLY_ACCESS) { - checkCB1Attestations(address).catch(console.error); + checkCB1Attestations(address).catch((error) => { + logError(error, 'Error checking CB1 attestation'); + }); } - }, [address, basenameChain.id]); + }, [address, basenameChain.id, logError]); const signature = cb1ProofResponse?.signedMessage as undefined | `0x${string}`; @@ -208,6 +218,7 @@ export function useCheckCB1Attestations() { } export function useCheckEAAttestations(): AttestationHookReturns { + const { logError } = useErrors(); const { address } = useAccount(); const [EAProofResponse, setEAProofResponse] = useState(null); const { basenameChain } = useBasenameChain(); @@ -223,15 +234,17 @@ export function useCheckEAAttestations(): AttestationHookReturns { const result = (await response.json()) as EarlyAccessProofResponse; setEAProofResponse(result); } - } catch (e) { - console.error('Error checking early access:', e); + } catch (error) { + logError(error, 'Error checking early access'); } } if (address) { - checkEarlyAccess(address).catch(console.error); + checkEarlyAccess(address).catch((error) => { + logError(error, 'Error checking early access'); + }); } - }, [address, basenameChain.id]); + }, [address, basenameChain.id, logError]); const encodedProof = useMemo( () => diff --git a/apps/web/src/hooks/useBasenameChain.ts b/apps/web/src/hooks/useBasenameChain.ts index cced5f4..94bbf30 100644 --- a/apps/web/src/hooks/useBasenameChain.ts +++ b/apps/web/src/hooks/useBasenameChain.ts @@ -5,6 +5,7 @@ import { createPublicClient, http } from 'viem'; import { cdpBaseRpcEndpoint, cdpBaseSepoliaRpcEndpoint } from 'apps/web/src/cdp/constants'; import { BaseName } from '@coinbase/onchainkit/identity'; import { getChainForBasename } from 'apps/web/src/utils/usernames'; +import { isDevelopment } from 'apps/web/src/constants'; export function getBasenamePublicClient(chainId: number) { const rpcEndpoint = chainId === baseSepolia.id ? cdpBaseSepoliaRpcEndpoint : cdpBaseRpcEndpoint; @@ -22,7 +23,6 @@ export function isBasenameSupportedChain(chainId: number) { } export default function useBasenameChain(username?: BaseName) { - const isDevelopment = process.env.NODE_ENV === 'development'; const { chain: connectedChain } = useAccount(); const chains = useChains(); @@ -37,7 +37,7 @@ export default function useBasenameChain(username?: BaseName) { // Not connected, default to Sepolia for development, base for other envs return isDevelopment ? baseSepolia : base; - }, [chains, connectedChain, isDevelopment, username]); + }, [chains, connectedChain, username]); const basenamePublicClient = getBasenamePublicClient(basenameChain.id); diff --git a/apps/web/src/hooks/useRegisterNameCallback.ts b/apps/web/src/hooks/useRegisterNameCallback.ts index 5892f3e..b233c8d 100644 --- a/apps/web/src/hooks/useRegisterNameCallback.ts +++ b/apps/web/src/hooks/useRegisterNameCallback.ts @@ -1,4 +1,5 @@ import { useAnalytics } from 'apps/web/contexts/Analytics'; +import { useErrors } from 'apps/web/contexts/Errors'; import L2ResolverAbi from 'apps/web/src/abis/L2Resolver'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; @@ -37,6 +38,7 @@ export function useRegisterNameCallback( ): UseRegisterNameCallbackReturnValue { const { address, chainId, isConnected } = useAccount(); const { basenameChain } = useBasenameChain(); + const { logError } = useErrors(); const { data: callBatchId, writeContractsAsync, @@ -129,7 +131,7 @@ export function useRegisterNameCallback( }); } } catch (e) { - console.error('failed to register name', e); + logError(e, 'Register name transaction canceled'); logEventWithContext('register_name_transaction_canceled', ActionType.change); } }, [ @@ -138,6 +140,7 @@ export function useRegisterNameCallback( capabilities, discountKey, isDiscounted, + logError, logEventWithContext, name, normalizedName,