Skip to content

Commit

Permalink
fix: improve web vitals (#250)
Browse files Browse the repository at this point in the history
Co-authored-by: Rosco Kalis <[email protected]>
  • Loading branch information
Dozie2001 and rkalis authored Jan 26, 2025
1 parent 7131da3 commit 23ccd34
Show file tree
Hide file tree
Showing 21 changed files with 71 additions and 63 deletions.
6 changes: 3 additions & 3 deletions app/Analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
'use client';

import { usePathname } from 'lib/i18n/navigation';
import { init, track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import Script from 'next/script';
import { useEffect } from 'react';

const Analytics = () => {
const path = usePathname();

useEffect(() => {
init();
analytics.init();
}, []);

useEffect(() => {
if (!path) return;
track('Viewed Page', { path });
analytics.track('Viewed Page', { path });
}, [path]);

// SimpleAnalytics
Expand Down
4 changes: 2 additions & 2 deletions components/common/Logo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import Image from 'next/image';
import { useState } from 'react';
import { memo, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import PlaceholderIcon from './PlaceholderIcon';

Expand Down Expand Up @@ -54,4 +54,4 @@ const Logo = ({ src, alt, size, square, border, className }: Props) => {
);
};

export default Logo;
export default memo(Logo);
12 changes: 2 additions & 10 deletions components/common/donate/DonateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,13 @@ const DonateButton = ({ size, style, className, type }: Props) => {
const t = useTranslations();
const [open, setOpen] = useState(false);

const handleOpen = () => {
setOpen(true);
};

const handleClose = () => {
setOpen(false);
};

return (
<>
<Button style={style ?? 'primary'} size={size} className={className} onClick={handleOpen}>
<Button style={style ?? 'primary'} size={size} className={className} onClick={() => setOpen(true)}>
{t('common.buttons.donate')}
</Button>

<DonateModal open={open} setOpen={(open) => (open ? handleOpen() : handleClose())} type={type} />
<DonateModal open={open} setOpen={setOpen} type={type} />
</>
);
};
Expand Down
4 changes: 2 additions & 2 deletions components/common/donate/DonateModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Button from 'components/common/Button';
import Modal from 'components/common/Modal';
import { useDonate } from 'lib/hooks/ethereum/useDonate';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { memo, useEffect, useState } from 'react';
import { useAsyncCallback } from 'react-async-hook';
import { useChainId } from 'wagmi';
import Input from '../Input';
Expand Down Expand Up @@ -79,4 +79,4 @@ const DonateModal = ({ open, setOpen, type }: Props) => {
);
};

export default DonateModal;
export default memo(DonateModal);
4 changes: 2 additions & 2 deletions components/exploits/ExploitChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from 'lib/hooks/page-context/AddressPageContext';
import { isNullish } from 'lib/utils';
import { getAllowanceKey } from 'lib/utils/allowances';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { getEventKey } from 'lib/utils/events';
import { type Exploit, getExploitStatus } from 'lib/utils/exploits';
import ExploitStatus from './ExploitStatus';
Expand All @@ -29,7 +29,7 @@ const ExploitChecker = ({ exploit }: Props) => {
queryKey: ['exploit-status', exploit.slug, allowances?.map(getAllowanceKey), events?.map(getEventKey)],
queryFn: () => {
const status = getExploitStatus(events!, allowances!, exploit);
track('Exploit Checked', { exploit: exploit.slug, account: address, chainId: selectedChainId, status });
analytics.track('Exploit Checked', { exploit: exploit.slug, account: address, chainId: selectedChainId, status });
return status;
},
enabled: !isNullish(address) && !isNullish(events) && !isNullish(allowances) && !isNullish(selectedChainId),
Expand Down
4 changes: 2 additions & 2 deletions components/footer/ColorThemeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ComputerDesktopIcon, MoonIcon, SunIcon } from '@heroicons/react/24/outl
import Select from 'components/common/select/Select';
import { useColorTheme } from 'lib/hooks/useColorTheme';
import { useMounted } from 'lib/hooks/useMounted';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { useTranslations } from 'next-intl';

const ColorThemeSelect = () => {
Expand All @@ -19,7 +19,7 @@ const ColorThemeSelect = () => {
] as const;

const selectTheme = (option: (typeof options)[number]) => {
track('Changed Color Theme', { theme: option.value });
analytics.track('Changed Color Theme', { theme: option.value });
setTheme(option.value);
};

Expand Down
4 changes: 2 additions & 2 deletions components/footer/LanguageSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Select from 'components/common/select/Select';
import type { Locale } from 'lib/i18n/config';
import { useCsrRouter } from 'lib/i18n/csr-navigation';
import { usePathname } from 'lib/i18n/navigation';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { useLocale } from 'next-intl';
import type { FormatOptionLabelMeta } from 'react-select';

Expand Down Expand Up @@ -36,7 +36,7 @@ const LanguageSelect = () => {

const selectLanguage = (option: Option) => {
const newLocale = option.value;
track('Changed language', { from: locale, to: newLocale });
analytics.track('Changed language', { from: locale, to: newLocale });
router.replace(path, { locale: newLocale, scroll: false, showProgress: false, retainSearchParams: ['chainId'] });
persistLocaleCookie(newLocale);
};
Expand Down
14 changes: 7 additions & 7 deletions components/header/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import AddressSearchBox from 'components/common/AddressSearchBox';
import Button from 'components/common/Button';
import { useCsrRouter } from 'lib/i18n/csr-navigation';
import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { useAccount } from 'wagmi';

Expand All @@ -16,21 +16,21 @@ const SearchBar = () => {
const { address } = useAccount();
const timerRef = useRef<NodeJS.Timeout>();

const onFocus = () => {
const onFocus = useCallback(() => {
clearTimeout(timerRef.current);
setIsFocused(true);
};
}, []);

const onBlur = () => {
const onBlur = useCallback(() => {
timerRef.current = setTimeout(() => setIsFocused(false), 200);
};
}, []);

const onClick = () => {
const onClick = useCallback(() => {
if (address) {
setValue(address);
router.push(`/address/${address}`, { retainSearchParams: ['chainId'] });
}
};
}, [address, router]);

return (
<div className="relative w-full">
Expand Down
5 changes: 3 additions & 2 deletions components/landing/DemoVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ const DemoVideo = () => {
controls
muted
loop
preload="metadata"
preload="metadata" // Preload the video fully for faster LCP
playsInline
poster="/assets/images/thumbnail/demo-thumbnail.webp"
>
<source src="/assets/videos/demo.mp4#t=0.001" type="video/mp4" />
<source src="/assets/videos/demo.mp4" type="video/mp4" />
</video>
);
};
Expand Down
4 changes: 2 additions & 2 deletions components/signatures/cells/CancelMarketplaceCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useHandleTransaction } from 'lib/hooks/ethereum/useHandleTransaction';
import { useAddressPageContext } from 'lib/hooks/page-context/AddressPageContext';
import { type Marketplace, type OnCancel, type TransactionSubmitted, TransactionType } from 'lib/interfaces';
import { waitForTransactionConfirmation } from 'lib/utils';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { usePublicClient, useWalletClient } from 'wagmi';
import CancelCell from './CancelCell';

Expand All @@ -21,7 +21,7 @@ const CancelMarketplaceCell = ({ marketplace, onCancel }: Props) => {
const sendCancelTransaction = async (): Promise<TransactionSubmitted> => {
const hash = await marketplace?.cancelSignatures(walletClient!);

track('Cancelled Marketplace Signatures', {
analytics.track('Cancelled Marketplace Signatures', {
chainId: selectedChainId,
account: address,
marketplace: marketplace.name,
Expand Down
8 changes: 6 additions & 2 deletions components/signatures/cells/CancelPermitCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useHandleTransaction } from 'lib/hooks/ethereum/useHandleTransaction';
import { useAddressPageContext } from 'lib/hooks/page-context/AddressPageContext';
import { type OnCancel, type TransactionSubmitted, TransactionType } from 'lib/interfaces';
import { waitForTransactionConfirmation } from 'lib/utils';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { permit } from 'lib/utils/permit';
import { type PermitTokenData, isErc721Contract } from 'lib/utils/tokens';
import { usePublicClient, useWalletClient } from 'wagmi';
Expand All @@ -25,7 +25,11 @@ const CancelPermitCell = ({ token, onCancel }: Props) => {
if (isErc721Contract(token.contract)) throw new Error('Cannot cancel ERC721 tokens');
const hash = await permit(walletClient!, token.contract, DUMMY_ADDRESS, 0n);

track('Cancelled Permit Signatures', { chainId: selectedChainId, account: address, token: token.contract.address });
analytics.track('Cancelled Permit Signatures', {
chainId: selectedChainId,
account: address,
token: token.contract.address,
});

const waitForConfirmation = async () => {
// TODO: Deduplicate this with the CancelMarketplaceCell
Expand Down
6 changes: 3 additions & 3 deletions lib/hooks/ethereum/EthereumProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { abstractWalletConnector } from '@abstract-foundation/agw-react/connecto
import { useCsrRouter } from 'lib/i18n/csr-navigation';
import { usePathname } from 'lib/i18n/navigation';
import { ORDERED_CHAINS, createViemPublicClientForChain, getViemChainConfig } from 'lib/utils/chains';
import { type ReactNode, useEffect } from 'react';
import { type ReactNode, memo, useEffect } from 'react';
import type { Chain } from 'viem';
import { WagmiProvider, createConfig, useAccount, useConnect } from 'wagmi';
import { coinbaseWallet, injected, safe, walletConnect } from 'wagmi/connectors';
Expand Down Expand Up @@ -50,7 +50,7 @@ export const EthereumProvider = ({ children }: Props) => {
);
};

const EthereumProviderChild = ({ children }: Props) => {
const EthereumProviderChild = memo(({ children }: Props) => {
const { connectAsync, connectors } = useConnect();
const { connector, address } = useAccount();
const router = useCsrRouter();
Expand Down Expand Up @@ -105,7 +105,7 @@ const EthereumProviderChild = ({ children }: Props) => {
}, [address]);

return <>{children}</>;
};
});

const isIframe = () => {
return typeof window !== 'undefined' && window?.parent !== window;
Expand Down
4 changes: 2 additions & 2 deletions lib/hooks/ethereum/useAllowances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
getAllowancesFromEvents,
stripAllowanceData,
} from 'lib/utils/allowances';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { type TimeLog, type TokenEvent, getEventKey } from 'lib/utils/events';
import { hasZeroBalance } from 'lib/utils/tokens';
import { useLayoutEffect, useState } from 'react';
Expand All @@ -30,7 +30,7 @@ export const useAllowances = (address: Address, events: TokenEvent[] | undefined
queryKey: ['allowances', address, chainId, events?.map(getEventKey)],
queryFn: async () => {
const allowances = getAllowancesFromEvents(address, events!, publicClient, chainId);
track('Fetched Allowances', { account: address, chainId });
analytics.track('Fetched Allowances', { account: address, chainId });
return allowances;
},
// If events (transfers + approvals) don't change, derived allowances also shouldn't change, even if allowances
Expand Down
4 changes: 2 additions & 2 deletions lib/hooks/ethereum/useDonate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { DonateButtonType } from 'components/common/donate/DonateModal';
import { DONATION_ADDRESS } from 'lib/constants';
import { type TransactionSubmitted, TransactionType } from 'lib/interfaces';
import { waitForTransactionConfirmation } from 'lib/utils';
import { track } from 'lib/utils/analytics';
import analytics from 'lib/utils/analytics';
import { type DocumentedChainId, getChainName, getChainNativeToken, getDefaultDonationAmount } from 'lib/utils/chains';
import { type SendTransactionParameters, parseEther } from 'viem';
import { usePublicClient, useWalletClient } from 'wagmi';
Expand Down Expand Up @@ -65,7 +65,7 @@ export const getTipSelection = (chainId: DocumentedChainId, amount: string) => {
export const trackDonate = (chainId: DocumentedChainId, amount: string, type: DonateButtonType) => {
if (!Number(amount)) return;

track('Donated', {
analytics.track('Donated', {
chainId,
chainName: getChainName(chainId),
nativeToken: getChainNativeToken(chainId),
Expand Down
6 changes: 3 additions & 3 deletions lib/utils/allowances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { type TransactionSubmitted, TransactionType } from 'lib/interfaces';
import type { TransactionStore } from 'lib/stores/transaction-store';
import { type Address, type PublicClient, type WalletClient, type WriteContractParameters, formatUnits } from 'viem';
import { deduplicateArray, isNullish, waitForTransactionConfirmation, writeContractUnlessExcessiveGas } from '.';
import { track } from './analytics';
import analytics from './analytics';
import { isNetworkError, isRevertedError, isUserRejectionError, parseErrorMessage, stringifyError } from './errors';
import {
type Erc20ApprovalEvent,
Expand Down Expand Up @@ -514,7 +514,7 @@ export const prepareUpdateErc20Allowance = async (

export const trackRevokeTransaction = (allowance: TokenAllowanceData, newAmount?: string) => {
if (isErc721Contract(allowance.contract)) {
track('Revoked ERC721 allowance', {
analytics.track('Revoked ERC721 allowance', {
chainId: allowance.chainId,
account: allowance.owner,
spender: allowance.payload?.spender,
Expand All @@ -525,7 +525,7 @@ export const trackRevokeTransaction = (allowance: TokenAllowanceData, newAmount?

const isRevoke = !newAmount || newAmount === '0';

track(isRevoke ? 'Revoked ERC20 allowance' : 'Updated ERC20 allowance', {
analytics.track(isRevoke ? 'Revoked ERC20 allowance' : 'Updated ERC20 allowance', {
chainId: allowance.chainId,
account: allowance.owner,
spender: allowance.payload?.spender,
Expand Down
29 changes: 20 additions & 9 deletions lib/utils/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import mixpanel from 'mixpanel-browser';

export const init = () => {
if (process.env.NEXT_PUBLIC_MIXPANEL_API_KEY) {
mixpanel.init(process.env.NEXT_PUBLIC_MIXPANEL_API_KEY, { ip: false });
}
};
const analytics = {
isInitialized: false,
// init only when first used
init() {
if (this.isInitialized) return;
const apiKey = process.env.NEXT_PUBLIC_MIXPANEL_API_KEY;
if (apiKey && typeof window !== 'undefined') {
mixpanel.init(apiKey, { ip: false });
this.isInitialized = true;
}
},

export const track = (eventName: string, eventProperties: any) => {
if (typeof window === 'undefined') return;
track(eventName: string, eventProperties?: Record<string, any>) {
if (typeof window === 'undefined' || !process.env.NEXT_PUBLIC_MIXPANEL_API_KEY) return;
// lazy initialize if not already done
if (!this.isInitialized) {
this.init();
}

if (process.env.NEXT_PUBLIC_MIXPANEL_API_KEY) {
mixpanel.track(eventName, eventProperties);
}
},
};

export default analytics;
4 changes: 2 additions & 2 deletions lib/utils/batch-revoke.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getTipSelection } from 'lib/hooks/ethereum/useDonate';
import type { TokenAllowanceData } from './allowances';
import { track } from './analytics';
import analytics from './analytics';

export type BatchType = 'eip5792' | 'queued';

Expand All @@ -11,7 +11,7 @@ export const trackBatchRevoke = (
tipAmount: string,
batchType: BatchType,
) => {
track('Batch Revoked', {
analytics.track('Batch Revoked', {
chainId,
address,
allowances: allowances.length,
Expand Down
4 changes: 2 additions & 2 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
pad,
slice,
} from 'viem';
import { track } from './analytics';
import analytics from './analytics';
import type { Log, TokenEvent } from './events';

export const assertFulfilled = <T>(item: PromiseSettledResult<T>): item is PromiseFulfilledResult<T> => {
Expand Down Expand Up @@ -126,7 +126,7 @@ export const throwIfExcessiveGas = (chainId: number, address: Address, estimated

// Track excessive gas usage so we can blacklist tokens
// TODO: Use a different tool than analytics for this
track('Excessive gas limit', { chainId, address, estimatedGas: estimatedGas.toString() });
analytics.track('Excessive gas limit', { chainId, address, estimatedGas: estimatedGas.toString() });

throw new Error(
'This transaction has an excessive gas cost. It is most likely a spam token, so you do not need to revoke this approval.',
Expand Down
Loading

0 comments on commit 23ccd34

Please sign in to comment.