Skip to content

Commit

Permalink
chore: refactor containers to use composition
Browse files Browse the repository at this point in the history
  • Loading branch information
pete-watters committed Jul 9, 2024
1 parent cf35f60 commit 99fc49e
Show file tree
Hide file tree
Showing 82 changed files with 870 additions and 769 deletions.
62 changes: 62 additions & 0 deletions src/app/common/page/page.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactNode, createContext, useContext, useEffect, useReducer } from 'react';

import { RouteUrls } from '@shared/route-urls';

interface HeaderPayloadState {
title?: string;
isSummaryPage?: boolean;
isSessionLocked?: boolean;
isSettingsVisibleOnSm?: boolean;
onBackLocation?: RouteUrls;
onClose?(): void;
}

interface UpdateAction {
type: 'update';
payload: HeaderPayloadState;
}

interface ResetAction {
type: 'reset';
}
type Action = UpdateAction | ResetAction;

const initialPageState = { isSessionLocked: false, isSettingsVisibleOnSm: true };
const pageReducer = (state: HeaderPayloadState, action: Action): HeaderPayloadState => {
switch (action.type) {
case 'update':
return { ...state, ...action.payload };
case 'reset':
default:
return initialPageState;
}
};

const PageContext = createContext<
{ state: HeaderPayloadState; dispatch: React.Dispatch<Action> } | undefined
>(undefined);

export function PageProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(pageReducer, initialPageState);
const value = { state, dispatch };
return <PageContext.Provider value={value}>{children}</PageContext.Provider>;
}

export const usePageContext = () => {
const context = useContext(PageContext);
if (context === undefined) {
throw new Error('usePageContext must be used within a PageProvider');
}
return context;
};

export function useUpdatePageHeaderContext(payload: HeaderPayloadState) {
const { dispatch } = usePageContext();

useEffect(() => {
dispatch({ type: 'update', payload });
return () => {
dispatch({ type: 'reset' });
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import get from 'lodash.get';

import { Button, Dialog } from '@leather.io/ui';

import { Footer } from '@app/ui/components/containers/footers/footer';
import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header';
import { Footer } from '@app/ui/layout/containers/footers/footer';
import { DialogHeader } from '@app/ui/layout/containers/headers/dialog-header';

export function BroadcastErrorDialog() {
const navigate = useNavigate();
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/request-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { analytics } from '@shared/utils/analytics';
import { useKeyActions } from '@app/common/hooks/use-key-actions';
import { buildEnterKeyEvent } from '@app/common/hooks/use-modifier-key';
import { WaitingMessages, useWaitingMessage } from '@app/common/hooks/use-waiting-message';
import { Footer } from '@app/ui/components/containers/footers/footer';
import { Card } from '@app/ui/layout/card/card';
import { Footer } from '@app/ui/layout/containers/footers/footer';
import { Page } from '@app/ui/layout/page/page.layout';

import { ErrorLabel } from './error-label';
Expand Down
2 changes: 2 additions & 0 deletions src/app/features/add-network/add-network.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Stack, styled } from 'leather-styles/jsx';
import { Button } from '@leather.io/ui';

import { ErrorLabel } from '@app/components/error-label';
import { useUpdatePageHeaderContext } from '@app/pages/shared/page.context';

Check failure on line 8 in src/app/features/add-network/add-network.tsx

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find module '@app/pages/shared/page.context' or its corresponding type declarations.
import { Card } from '@app/ui/layout/card/card';
import { Page } from '@app/ui/layout/page/page.layout';

Expand All @@ -13,6 +14,7 @@ import { useAddNetwork } from './use-add-network';

export function AddNetwork() {
const { error, initialFormValues, loading, onSubmit } = useAddNetwork();
useUpdatePageHeaderContext({ title: 'Add Network' });

return (
<Page>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function Sip10TokenAssetList({
{tokens.map(token => (
<Sip10TokenAssetItem
balance={token.balance}
key={token.info.name}
key={token.info.name + token.info.contractId}
info={token.info}
isLoading={isLoading}
marketData={priceAsMarketData(
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BitcoinCustomFee } from '@app/components/bitcoin-custom-fee/bitcoin-cus
import { MAX_FEE_RATE_MULTIPLIER } from '@app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee';
import { OnChooseFeeArgs } from '@app/components/bitcoin-fees-list/bitcoin-fees-list';
import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance';
import { AvailableBalance } from '@app/ui/layout/containers/footers/available-balance';

import { BitcoinChooseFeeLayout } from './components/bitcoin-choose-fee.layout';
import { ChooseFeeSubtitle } from './components/choose-fee-subtitle';
Expand Down
16 changes: 16 additions & 0 deletions src/app/features/container/components/container.layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Flex } from 'leather-styles/jsx';

interface ContainerLayoutProps {
content?: React.JSX.Element | React.JSX.Element[];
header?: React.JSX.Element | null;
}
export function ContainerLayout({ content, header }: ContainerLayoutProps) {
return (
<Flex flexDirection="column" flexGrow={1} width="100%" height={{ base: '100vh', sm: '100%' }}>
{header}
<Flex className="main-content" flexGrow={1} position="relative" width="100%">
{content}
</Flex>
</Flex>
);
}
53 changes: 53 additions & 0 deletions src/app/features/container/components/home/home-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ReactNode, useState } from 'react';

import { SettingsSelectors } from '@tests/selectors/settings.selectors';
import { Flex, Grid, GridItem, HStack, styled } from 'leather-styles/jsx';

import { HamburgerIcon } from '@leather.io/ui';

import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog';

import { Settings } from '../../../settings/settings';

interface HomeHeaderProps {
networkBadge?: ReactNode;
logo?: ReactNode;
}

export function HomeHeader({ networkBadge, logo }: HomeHeaderProps) {
const [isShowingSwitchAccount, setIsShowingSwitchAccount] = useState(false);
return (
<>
{isShowingSwitchAccount && (
<SwitchAccountDialog
isShowing={isShowingSwitchAccount}
onClose={() => setIsShowingSwitchAccount(false)}
/>
)}
<styled.header
justifyContent="center"
margin={{ base: 0, md: 'auto' }}
p="space.04"
bg="transparent"
maxWidth={{ base: '100vw', md: 'fullPageMaxWidth' }}
width="100%"
>
<Grid alignItems="center" gridTemplateColumns="auto" gridAutoFlow="column" width="100%">
<GridItem justifySelf="start">
<Flex py={{ base: 0, md: 'space.01' }}>{logo}</Flex>
</GridItem>

<GridItem>
<HStack alignItems="center" justifyContent="flex-end">
{networkBadge}
<Settings
triggerButton={<HamburgerIcon data-testid={SettingsSelectors.SettingsMenuBtn} />}
toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)}
/>
</HStack>
</GridItem>
</Grid>
</styled.header>
</>
);
}
36 changes: 36 additions & 0 deletions src/app/features/container/components/home/home.layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Outlet } from 'react-router-dom';

import { ChainID } from '@stacks/transactions';
import { Box } from 'leather-styles/jsx';

import { Logo, NetworkModeBadge } from '@leather.io/ui';

import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';

import { ContainerLayout } from '../container.layout';
import { HomeHeader } from './home-header';

export function HomeLayout() {
const { chain, name: chainName } = useCurrentNetworkState();

return (
<ContainerLayout
header={
<HomeHeader
networkBadge={
<NetworkModeBadge
isTestnetChain={chain.stacks.chainId === ChainID.Testnet}
name={chainName}
/>
}
logo={
<Box height="headerContainerHeight" margin="auto" px="space.02">
<Logo />
</Box>
}
/>
}
content={<Outlet />}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ReactNode } from 'react';

import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors';
import { Flex, Grid, GridItem, HStack, styled } from 'leather-styles/jsx';

import { ArrowLeftIcon } from '@leather.io/ui';

import { HeaderActionButton } from '../../../../ui/layout/containers/headers/components/header-action-button';

interface HeaderProps {
onClose?(): void;
onGoBack?(): void;
title?: ReactNode;
settingsMenu?: ReactNode;
networkBadge?: ReactNode;
logo?: ReactNode;
}

export function OnboardingHeader({ onGoBack, networkBadge, logo }: HeaderProps) {
const logoItem = onGoBack || logo;

return (
<styled.header
justifyContent="center"
margin={{ base: 0, md: 'auto' }}
p="space.04"
paddingLeft={{ base: undefined, sm: 0 }}
bg="transparent"
maxWidth={{ base: '100vw', md: 'fullPageMaxWidth' }}
width="100%"
>
<Grid alignItems="center" gridTemplateColumns="auto" gridAutoFlow="column" width="100%">
<GridItem justifySelf="start">
{logoItem && (
<Flex py={{ base: 0, md: 'space.01' }}>
{onGoBack && (
<HeaderActionButton
icon={<ArrowLeftIcon />}
onAction={onGoBack}
dataTestId={SharedComponentsSelectors.HeaderBackBtn}
/>
)}
{logo}
</Flex>
)}
</GridItem>

<GridItem>
<HStack alignItems="center" justifyContent="flex-end">
{networkBadge}
</HStack>
</GridItem>
</Grid>
</styled.header>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Outlet, useLocation, useNavigate } from 'react-router-dom';

import { ChainID } from '@stacks/transactions';
import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors';
import { Box } from 'leather-styles/jsx';

import { Logo, NetworkModeBadge } from '@leather.io/ui';

import { RouteUrls } from '@shared/route-urls';

import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';

import { ContainerLayout } from '../container.layout';
import { OnboardingHeader } from './onboarding-header';

export function OnboardingLayout() {
const navigate = useNavigate();
const { pathname: locationPathname } = useLocation();
const pathname = locationPathname as RouteUrls;

const { chain, name: chainName } = useCurrentNetworkState();

const displayHeader = !pathname.match(RouteUrls.Onboarding);

return (
<ContainerLayout
header={
displayHeader ? (
<OnboardingHeader
onGoBack={() => navigate(-1)}
networkBadge={
<NetworkModeBadge
isTestnetChain={chain.stacks.chainId === ChainID.Testnet}
name={chainName}
/>
}
logo={
// TODO: RouteUrls.ViewSecretKey needs show logo when viewing key but not when entering password
pathname !== RouteUrls.ViewSecretKey && (
<Box height="headerContainerHeight" margin="auto" px="space.02">
<Logo
data-testid={OnboardingSelectors.LogoRouteToHome}
onClick={() => navigate(RouteUrls.Home)}
/>
</Box>
)
}
/>
) : null
}
content={<Outlet />}
/>
);
}
Loading

0 comments on commit 99fc49e

Please sign in to comment.