Skip to content

Commit

Permalink
fix(shell): use dynamic components loading to prevent hydration errors
Browse files Browse the repository at this point in the history
  • Loading branch information
olegshilov committed May 16, 2024
1 parent c0d6a2d commit c1a6cae
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 369 deletions.
17 changes: 13 additions & 4 deletions apps/shell/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SpeedInsights } from '@vercel/speed-insights/next';
import clsx from 'clsx';
import type { Metadata, Viewport } from 'next';
import dynamic from 'next/dynamic';
import { headers } from 'next/headers';
import type { Config } from '@haqq/shell-shared';
import { AppWrapper } from '../components/app-wrapper';
import { PHProvider } from '../components/posthog';
Expand Down Expand Up @@ -46,12 +47,20 @@ const PostHogPageView = dynamic(
const { PostHogPageView } = await import('../components/posthog-page-view');
return { default: PostHogPageView };
},
{
ssr: false,
},
{ ssr: false },
);

export default function RootLayout({ children }: PropsWithChildren) {
const headersList = headers();
const userAgent = headersList.get('user-agent');
const isMobileUserAgent = userAgent
? Boolean(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i,
),
)
: false;

return (
<html
lang="en"
Expand All @@ -63,7 +72,7 @@ export default function RootLayout({ children }: PropsWithChildren) {
<PostHogPageView />
<SpeedInsights />

<Providers config={shellConfig}>
<Providers config={shellConfig} isMobileUserAgent={isMobileUserAgent}>
<AppWrapper>{children}</AppWrapper>
</Providers>
</body>
Expand Down
250 changes: 3 additions & 247 deletions apps/shell/src/components/app-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,26 @@
'use client';
import {
Fragment,
PropsWithChildren,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import clsx from 'clsx';
import { useRouter } from 'next/navigation';
import { usePostHog } from 'posthog-js/react';
import { useMediaQuery } from 'react-responsive';
import { useScrollLock } from 'usehooks-ts';
import { useBalance, useConnect, useSwitchNetwork, useNetwork } from 'wagmi';
import { useConnect, useNetwork } from 'wagmi';
import { haqqTestedge2 } from 'wagmi/chains';
import {
useAddress,
useWallet,
getFormattedAddress,
useSupportedChains,
useConfig,
} from '@haqq/shell-shared';
import { useWallet, useSupportedChains, useConfig } from '@haqq/shell-shared';
import {
Page,
Header,
AccountButton,
HeaderNavLink,
Button,
BurgerButton,
SelectChainButton,
SelectWalletModal,
TestedgeBanner,
formatNumber,
Footer,
CommitSha,
SelectChainModal,
LowBalanceAlert,
} from '@haqq/shell-ui-kit';
import { HeaderButtons } from '../components/header-buttons';
import { useHaqqWalletAutoConnect } from '../hooks/use-autoconnect';

declare const window: Window &
Expand All @@ -46,234 +30,6 @@ declare const window: Window &
};
};

function HeaderButtons({
isMobileMenuOpen,
onMobileMenuOpenChange,
isTestedge,
}: {
isMobileMenuOpen: boolean;
onMobileMenuOpenChange: (isMobileMenuOpen: boolean) => void;
isTestedge: boolean;
}) {
const { commitSha } = useConfig();
const chains = useSupportedChains();
const { chain = chains[0] } = useNetwork();
const { disconnect, openSelectWallet, isNetworkSupported, selectNetwork } =
useWallet();
const { ethAddress } = useAddress();
const { data: balanceData } = useBalance({
address: ethAddress,
chainId: chain.id,
});
const { switchNetworkAsync } = useSwitchNetwork();
const isDesktop = useMediaQuery({
query: `(min-width: 1024px)`,
});
const router = useRouter();
const { isHaqqWallet } = useWallet();
const { lock, unlock } = useScrollLock();

const handleChainSelectClick = useCallback(
async (chainId: number) => {
if (switchNetworkAsync) {
await switchNetworkAsync(chainId);
router.push('/');
}
},
[router, switchNetworkAsync],
);

const balance = useMemo(() => {
if (!balanceData) {
return undefined;
}

return {
symbol: balanceData.symbol,
value: formatNumber(Number.parseFloat(balanceData.formatted)),
};
}, [balanceData]);

const selectChainButtonProps = useMemo(() => {
return {
isSupported: isNetworkSupported,
currentChain: {
name: chain.name.replace('HAQQ', '').trim() ?? '',
id: chain.id ?? 0,
},
chains: chains.map((chain) => {
return {
name: chain.name.replace('HAQQ', '').trim(),
id: chain.id,
};
}),
};
}, [chain.id, chain.name, chains, isNetworkSupported]);

useEffect(() => {
if (isDesktop) {
onMobileMenuOpenChange(false);
}
}, [isDesktop, onMobileMenuOpenChange]);

useEffect(() => {
if (isMobileMenuOpen) {
lock();
} else {
unlock();
}
}, [isMobileMenuOpen, lock, unlock]);

return (
<Fragment>
<nav className="hidden flex-row items-center space-x-6 lg:flex">
<HeaderNavLink href="/staking">Staking</HeaderNavLink>
<HeaderNavLink href="/governance">Governance</HeaderNavLink>
<HeaderNavLink href="/authz">Authz</HeaderNavLink>
{isTestedge && <HeaderNavLink href="/faucet">Faucet</HeaderNavLink>}
</nav>

<div className="hidden pl-[80px] lg:block">
{ethAddress ? (
<div className="flex flex-row gap-[24px]">
<SelectChainButton
{...selectChainButtonProps}
onChainSelect={async (chainId) => {
await selectNetwork(chainId);
router.push('/');
}}
/>
<AccountButton
balance={balance}
address={getFormattedAddress(ethAddress, 3, 2)}
onDisconnectClick={disconnect}
/>
</div>
) : (
<Button onClick={openSelectWallet}>Connect wallet</Button>
)}
</div>

<div className="block leading-[0] lg:hidden">
<BurgerButton
isOpen={isMobileMenuOpen}
onClick={() => {
onMobileMenuOpenChange(!isMobileMenuOpen);
}}
className="h-[24px] w-[24px] sm:h-[30px] sm:w-[30px]"
/>
</div>

{isMobileMenuOpen && (
<Fragment>
<div
className={clsx(
'bg-haqq-black fixed right-0 z-40 w-full transform-gpu lg:hidden',
isTestedge
? 'top-[61px] h-[calc(100vh-101px)] sm:top-[71px] sm:h-[calc(100vh-111px)]'
: 'top-[61px] h-[calc(100vh-61px)] sm:top-[71px] sm:h-[calc(100vh-71px)]',
)}
>
<div className="overflow-y-auto px-[24px] py-[32px]">
<div className="mb-[24px] flex flex-col items-start gap-[16px] sm:mb-[80px]">
{isHaqqWallet && (
<div>
<HeaderNavLink
href="/"
onClick={() => {
onMobileMenuOpenChange(false);
}}
>
Home
</HeaderNavLink>
</div>
)}

<div>
<HeaderNavLink
href="/staking"
onClick={() => {
onMobileMenuOpenChange(false);
}}
>
Staking
</HeaderNavLink>
</div>
<div>
<HeaderNavLink
href="/governance"
onClick={() => {
onMobileMenuOpenChange(false);
}}
>
Governance
</HeaderNavLink>
</div>
<div>
<HeaderNavLink
href="/authz"
onClick={() => {
onMobileMenuOpenChange(false);
}}
>
Authz
</HeaderNavLink>
</div>
{isTestedge && (
<div>
<HeaderNavLink
href="/faucet"
onClick={() => {
onMobileMenuOpenChange(false);
}}
>
Faucet
</HeaderNavLink>
</div>
)}
</div>

{ethAddress && (
<div className="flex flex-col gap-[24px]">
<div>
<SelectChainButton
{...selectChainButtonProps}
onChainSelect={handleChainSelectClick}
/>
</div>
<AccountButton
balance={balance}
address={getFormattedAddress(ethAddress, 3, 2)}
onDisconnectClick={disconnect}
withoutDropdown
/>
</div>
)}

<div className="mt-[24px]">
{ethAddress ? (
<Button onClick={disconnect}>Disconnect</Button>
) : (
<Button onClick={openSelectWallet}>Connect wallet</Button>
)}
</div>
</div>

<div className="absolute bottom-[8px] left-[20px]">
<div className="text-[12px] leading-[16px] text-white/20">
<CommitSha
commitSha={commitSha}
className="transition-colors duration-150 hover:text-white/80"
/>
</div>
</div>
</div>
</Fragment>
)}
</Fragment>
);
}

export function AppWrapper({ children }: PropsWithChildren) {
const { commitSha } = useConfig();
const [isMobileMenuOpen, setMobileMenuOpen] = useState(false);
Expand Down
Loading

0 comments on commit c1a6cae

Please sign in to comment.