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 e258fbd
Show file tree
Hide file tree
Showing 82 changed files with 870 additions and 769 deletions.
36 changes: 36 additions & 0 deletions src/app/common/page/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 '../../features/container/components/container.layout';
import { HomeHeader } from '../../features/container/components/home/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 />}
/>
);
}
54 changes: 54 additions & 0 deletions src/app/common/page/onboarding.layout.tsx
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 '../../features/container/components/container.layout';
import { OnboardingHeader } from '../../features/container/components/onboarding/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 />}
/>
);
}
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' });
};
});
}
13 changes: 13 additions & 0 deletions src/app/common/page/page.layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Outlet } from 'react-router-dom';

import { ContainerLayout } from '../../features/container/components/container.layout';
import { PageHeader } from '../../features/container/components/page/page.header';
import { PageProvider } from './page.context';

export function PageLayout() {
return (
<PageProvider>
<ContainerLayout header={<PageHeader />} content={<Outlet />} />
</PageProvider>
);
}
106 changes: 106 additions & 0 deletions src/app/common/page/popup.layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useState } from 'react';
import { Outlet, useLocation } from 'react-router-dom';

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

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

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

import { CurrentAccountAvatar } from '@app/features/current-account/current-account-avatar';
import { CurrentAccountName } from '@app/features/current-account/current-account-name';
import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';

import { ContainerLayout } from '../../features/container/components/container.layout';
import { PopupHeader } from '../../features/container/components/popup/popup-header';
import { TotalBalance } from '../../features/container/total-balance';

function showHeader(pathname: RouteUrls) {
switch (pathname) {
case RouteUrls.RpcGetAddresses:
case RouteUrls.ChooseAccount:
return false;
default:
return true;
}
}

function showAccountInfo(pathname: RouteUrls) {
switch (pathname) {
case RouteUrls.TransactionRequest:
case RouteUrls.ProfileUpdateRequest:
case RouteUrls.RpcSendTransfer:
return true;
default:
return false;
}
}

function showBalanceInfo(pathname: RouteUrls) {
switch (pathname) {
case RouteUrls.ProfileUpdateRequest:
case RouteUrls.RpcSendTransfer:
return true;
default:
return false;
}
}

export function PopupLayout() {
const [isShowingSwitchAccount, setIsShowingSwitchAccount] = useState(false);
const { pathname: locationPathname } = useLocation();
const pathname = locationPathname as RouteUrls;

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

const isHeaderVisible = showHeader(pathname);

return (
<>
{isShowingSwitchAccount && (
<SwitchAccountDialog
isShowing={isShowingSwitchAccount}
onClose={() => setIsShowingSwitchAccount(false)}
/>
)}
<ContainerLayout
header={
isHeaderVisible ? (
<PopupHeader
networkBadge={
<NetworkModeBadge
isTestnetChain={chain.stacks.chainId === ChainID.Testnet}
name={chainName}
/>
}
logo={
<Box height="headerPopupHeight" margin="auto" px="space.02">
<Logo />
</Box>
}
account={
showAccountInfo(pathname) && (
<Flag
align="middle"
img={<CurrentAccountAvatar />}
onClick={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)}
cursor="pointer"
>
<CurrentAccountName />
</Flag>
)
}
totalBalance={
// We currently only show displayAddresssBalanceOf="all" in the total balance component
showBalanceInfo(pathname) && <TotalBalance displayAddresssBalanceOf="all" />
}
/>
) : undefined
}
content={<Outlet />}
/>
</>
);
}
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 @@ -4,6 +4,7 @@ import { Stack, styled } from 'leather-styles/jsx';

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

import { useUpdatePageHeaderContext } from '@app/common/page/page.context';
import { ErrorLabel } from '@app/components/error-label';
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>
);
}
Loading

0 comments on commit e258fbd

Please sign in to comment.