Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Nakamoto homepage blocks view #1531

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/PageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { TxListTabs } from '../features/txs-list/tabs/TxListTabs';
import { Grid } from '../ui/Grid';
import { SkeletonBlockList } from './_components/BlockList/SkeletonBlockList';
import { UpdatedBlocksList } from './_components/BlockList/UpdatedBlockList';

Check warning on line 10 in src/app/PageClient.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/PageClient.tsx#L10

Added line #L10 was not covered by tests
import { PageTitle } from './_components/PageTitle';
import { Stats } from './_components/Stats/Stats';

Expand Down Expand Up @@ -40,7 +41,7 @@
<TxListTabs limit={DEFAULT_LIST_LIMIT_SMALL} />

{activeNetworkKey.indexOf('naka') !== -1 ? (
<NonPaginatedBlockListLayoutA />
<UpdatedBlocksList limit={DEFAULT_BLOCKS_LIST_LIMIT} />
) : (
<BlocksList limit={DEFAULT_BLOCKS_LIST_LIMIT} />
)}
Expand Down
7 changes: 4 additions & 3 deletions src/app/_components/BlockList/LayoutA/BurnBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import { Timestamp } from '../../../../common/components/Timestamp';
import { useGlobalContext } from '../../../../common/context/useAppContext';
import { truncateMiddle } from '../../../../common/utils/utils';
import { Box } from '../../../../ui/Box';
import { Flex } from '../../../../ui/Flex';
import { Flex, FlexProps } from '../../../../ui/Flex';
import { HStack } from '../../../../ui/HStack';
import { Icon } from '../../../../ui/Icon';
import { Text } from '../../../../ui/Text';
import { TextLink } from '../../../../ui/TextLink';
import { BitcoinIcon } from '../../../../ui/icons';

interface ListItemProps {
interface ListItemProps extends FlexProps {
height: number | string;
hash: string;
timestamp?: number;
}
export const BurnBlock = memo(function ({ timestamp, height, hash }: ListItemProps) {
export const BurnBlock = memo(function ({ timestamp, height, hash, ...flexProps }: ListItemProps) {
const { btcBlockBaseUrl } = useGlobalContext().activeNetwork;
const bgColor = useColorModeValue('slate.150', 'slate.900');
const textColor = useColorModeValue('slate.700', 'slate.500');
Expand All @@ -36,6 +36,7 @@ export const BurnBlock = memo(function ({ timestamp, height, hash }: ListItemPro
mr={'-8'}
ml={'-10'}
color={textColor}
{...flexProps}
>
<HStack gap={1.5}>
<Icon
Expand Down
239 changes: 239 additions & 0 deletions src/app/_components/BlockList/UpdatedBlockList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
'use client';

import { useColorModeValue } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';

Check warning on line 4 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L3-L4

Added lines #L3 - L4 were not covered by tests
import pluralize from 'pluralize';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';

Check warning on line 6 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L6

Added line #L6 was not covered by tests

import { connectWebSocketClient } from '@stacks/blockchain-api-client';

Check warning on line 8 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L8

Added line #L8 was not covered by tests
import { Block } from '@stacks/stacks-blockchain-api-types';

import { BtcStxBlockLinks } from '../../../common/components/BtcStxBlockLinks';
import { ListFooter } from '../../../common/components/ListFooter';
import { Section } from '../../../common/components/Section';

Check warning on line 13 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L12-L13

Added lines #L12 - L13 were not covered by tests
import { TwoColsListItem } from '../../../common/components/TwoColumnsListItem';
import { SkeletonBlockList } from '../../../common/components/loaders/skeleton-text';
import { DEFAULT_LIST_LIMIT } from '../../../common/constants/constants';
import { useGlobalContext } from '../../../common/context/useAppContext';
import { useSuspenseInfiniteQueryResult } from '../../../common/hooks/useInfiniteQueryResult';
import { useSuspenseBlockListInfinite } from '../../../common/queries/useBlockListInfinite';

Check warning on line 19 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L15-L19

Added lines #L15 - L19 were not covered by tests
import { addSepBetweenStrings, toRelativeTime, truncateMiddle } from '../../../common/utils/utils';
import { Accordion } from '../../../ui/Accordion';
import { Box } from '../../../ui/Box';
import { Collapse } from '../../../ui/Collapse';

Check warning on line 23 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L22-L23

Added lines #L22 - L23 were not covered by tests
import { Flex, FlexProps } from '../../../ui/Flex';
import { FormControl } from '../../../ui/FormControl';
import { FormLabel } from '../../../ui/FormLabel';
import { Icon } from '../../../ui/Icon';
import { Stack } from '../../../ui/Stack';
import { Switch } from '../../../ui/Switch';
import { StxIcon } from '../../../ui/icons';

Check warning on line 30 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L25-L30

Added lines #L25 - L30 were not covered by tests
import { Caption } from '../../../ui/typography';
import { ExplorerErrorBoundary } from '../ErrorBoundary';
import { BurnBlock } from './LayoutA/BurnBlock';
import { StxBlock } from './LayoutA/StxBlock';

Check warning on line 34 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L32-L34

Added lines #L32 - L34 were not covered by tests
import { EnhancedBlock } from './types';

export const animationDuration = 0.8;

Check warning on line 37 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L37

Added line #L37 was not covered by tests

export const BlockListItem: React.FC<{ block: Block } & FlexProps> = React.memo(
({ block, ...rest }) => {

Check warning on line 40 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L39-L40

Added lines #L39 - L40 were not covered by tests
return (
<>
<StxBlock
key={block.hash}
hash={block.hash}
height={block.height}
timestamp={block.burn_block_time}
txsCount={block.txs.length}
icon={<Icon as={StxIcon} size={2.5} color={'white'} />}
/>
<BurnBlock
mr={'-6'}
ml={'-6'}
pl={5}
pr={6}
key={block.hash}
hash={block.hash}
height={block.height}
timestamp={block.burn_block_time}
/>
</>
);
}
);

export const AnimatedBlockAndMicroblocksItem: FC<{

Check warning on line 66 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L66

Added line #L66 was not covered by tests
block: EnhancedBlock;
onAnimationExit?: () => void;
}> = ({ block, onAnimationExit }) => {
const [show, setShow] = useState(!block.animate);
useEffect(() => {

Check warning on line 71 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L69-L71

Added lines #L69 - L71 were not covered by tests
if (block.animate) {
setTimeout(() => {
setShow(true);

Check warning on line 74 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L73-L74

Added lines #L73 - L74 were not covered by tests
}, 100);
}
}, [block.animate]);
useEffect(() => {

Check warning on line 78 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L78

Added line #L78 was not covered by tests
if (block.destroy) {
setShow(false);

Check warning on line 80 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L80

Added line #L80 was not covered by tests
}
}, [block.destroy]);

return (
<Collapse
in={show}
animateOpacity
transition={{
enter: { duration: animationDuration },
exit: { duration: animationDuration },
}}
onAnimationComplete={state => {

Check warning on line 92 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L92

Added line #L92 was not covered by tests
if (state === 'exit') {
onAnimationExit?.();

Check warning on line 94 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L94

Added line #L94 was not covered by tests
}
}}
data-testid={`block-item-${block.hash}`}
style={{
overflow: 'unset',
}}
>
<BlockAndMicroblocksItem block={block} />
</Collapse>
);
};

export const BlockAndMicroblocksItem: React.FC<{ block: Block }> = ({ block }) => {

Check warning on line 107 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L107

Added line #L107 was not covered by tests
return <BlockListItem block={block} data-test={`block-${block.hash}`} />;
};

function UpdatedBlocksListBase({
limit,
}: {
limit?: number;
} & FlexProps) {
const [isLive, setIsLive] = React.useState(false);
const [initialBlocks, setInitialBlocks] = useState<EnhancedBlock[]>([]);
const [latestBlocks, setLatestBlocks] = useState<EnhancedBlock[]>([]);
const activeNetwork = useGlobalContext().activeNetwork;
const response = useSuspenseBlockListInfinite();
const { isFetchingNextPage, fetchNextPage, hasNextPage } = response;
const queryClient = useQueryClient();

Check warning on line 122 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L115-L122

Added lines #L115 - L122 were not covered by tests

const blocks = useSuspenseInfiniteQueryResult<Block>(response, limit);

Check warning on line 124 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L124

Added line #L124 was not covered by tests

const labelColor = useColorModeValue('slate.600', 'slate.400');

Check warning on line 126 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L126

Added line #L126 was not covered by tests

useEffect(() => {
setInitialBlocks(blocks);

Check warning on line 129 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L128-L129

Added lines #L128 - L129 were not covered by tests
}, [blocks]);

useEffect(() => {

Check warning on line 132 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L132

Added line #L132 was not covered by tests
if (!isLive) return;
void queryClient.invalidateQueries({ queryKey: ['blockListInfinite'] });

Check warning on line 134 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L134

Added line #L134 was not covered by tests
let sub: {
unsubscribe?: () => Promise<void>;
};
const subscribe = async () => {
const client = await connectWebSocketClient(activeNetwork.url.replace('https://', 'wss://'));
sub = await client.subscribeBlocks((block: any) => {
setLatestBlocks(prevLatestBlocks => [

Check warning on line 141 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L138-L141

Added lines #L138 - L141 were not covered by tests
{ ...block, microblock_tx_count: {}, animate: true },
...prevLatestBlocks,
]);
});
};
void subscribe();
return () => {

Check warning on line 148 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L147-L148

Added lines #L147 - L148 were not covered by tests
if (sub?.unsubscribe) {
void sub.unsubscribe();

Check warning on line 150 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L150

Added line #L150 was not covered by tests
}
};
}, [activeNetwork.url, isLive, queryClient]);

const allBlocks = useMemo(() => {
return [...latestBlocks, ...initialBlocks]

Check warning on line 156 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L155-L156

Added lines #L155 - L156 were not covered by tests
.sort((a, b) => (b.height || 0) - (a.height || 0))
.reduce((acc: EnhancedBlock[], block, index) => {

Check warning on line 158 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L158

Added line #L158 was not covered by tests
if (!acc.some(b => b.height === block.height)) {
acc.push({ ...block, destroy: index >= (limit || DEFAULT_LIST_LIMIT) });
}
return acc;

Check warning on line 162 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L162

Added line #L162 was not covered by tests
}, []);
}, [initialBlocks, latestBlocks, limit]);

const removeOldBlock = useCallback((block: EnhancedBlock) => {
setInitialBlocks(prevBlocks => prevBlocks.filter(b => b.height !== block.height));
setLatestBlocks(prevBlocks => prevBlocks.filter(b => b.height !== block.height));

Check warning on line 168 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L166-L168

Added lines #L166 - L168 were not covered by tests
}, []);

if (!allBlocks?.length) return <SkeletonBlockList />;

return (
<Section
title="Recent Blocks"
gridColumnStart={['1', '1', '1', '2']}
gridColumnEnd={['2', '2', '2', '3']}
minWidth={0}
flexGrow={0}
flexShrink={1}
topRight={
<FormControl display="flex" alignItems="center">
<FormLabel htmlFor="blocks-live-view-switch" mb="0" color={labelColor}>
live view
</FormLabel>
<Switch
id="blocks-live-view-switch"
isChecked={isLive}
onChange={() => setIsLive(!isLive)}

Check warning on line 189 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L189

Added line #L189 was not covered by tests
/>
</FormControl>
}
>
<Stack pb={6} gap={5}>
<Box>
{allBlocks?.map(block =>
isLive ? (
<AnimatedBlockAndMicroblocksItem

Check warning on line 198 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L198

Added line #L198 was not covered by tests
block={block}
key={block.hash}
onAnimationExit={() => removeOldBlock(block)}

Check warning on line 201 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L201

Added line #L201 was not covered by tests
/>
) : (
<BlockAndMicroblocksItem block={block} key={block.hash} />

Check warning on line 204 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L204

Added line #L204 was not covered by tests
)
)}
</Box>
<Box>
{!isLive && (
<ListFooter
isLoading={isFetchingNextPage}
hasNextPage={hasNextPage}
href={limit ? '/blocks' : undefined}
fetchNextPage={limit ? undefined : fetchNextPage}
label={'blocks'}
/>
)}
</Box>
</Stack>
</Section>
);
}

export function UpdatedBlocksList({ limit }: { limit?: number }) {

Check warning on line 224 in src/app/_components/BlockList/UpdatedBlockList.tsx

View check run for this annotation

Codecov / codecov/patch

src/app/_components/BlockList/UpdatedBlockList.tsx#L224

Added line #L224 was not covered by tests
return (
<ExplorerErrorBoundary
Wrapper={Section}
wrapperProps={{
title: 'Recent Blocks',
gridColumnStart: ['1', '1', '2'],
gridColumnEnd: ['2', '2', '3'],
minWidth: 0,
}}
tryAgainButton
>
<UpdatedBlocksListBase limit={limit} />
</ExplorerErrorBoundary>
);
}