Commit 3bbeec15 authored by tom's avatar tom

tac ui updates

parent ab048643
......@@ -4,7 +4,7 @@ import { ADDRESS_HASH } from './addressParams';
export const TAC_OPERATION: tac.OperationBriefDetails = {
operation_id: '0x4d3d36b7fcab0a2f93f24bf313ebfe9cc0b2c7157d2aef7e7f7d5835528428c6',
type: tac.OperationType.PENDING,
type: tac.OperationType.TAC_TON,
timestamp: '2025-05-05T12:32:22.000Z',
sender: {
address: '0x4d3d36b7fcab0a2f93f24bf313ebfe9cc0b2c7157d2aef7e7f7d5835528428c6',
......@@ -14,7 +14,7 @@ export const TAC_OPERATION: tac.OperationBriefDetails = {
export const TAC_OPERATION_DETAILS: tac.OperationDetails = {
operation_id: '0x6e7cdeea3f39e7664597a44ddb33ce47ba061cbee2992e2c7b0e3f9294ff8b30',
type: tac.OperationType.PENDING,
type: tac.OperationType.TAC_TON,
timestamp: '2025-05-05T12:32:22.000Z',
sender: {
address: ADDRESS_HASH,
......
......@@ -66,7 +66,7 @@ const TacOperationDetails = ({ isLoading, data }: Props) => {
Lifecycle
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<TacOperationLifecycleAccordion data={ statusHistory } isLoading={ isLoading }/>
<TacOperationLifecycleAccordion data={ statusHistory } isLoading={ isLoading } type={ data.type }/>
</DetailedInfo.ItemValue>
</>
) }
......
import React from 'react';
import type * as tac from '@blockscout/tac-operation-lifecycle-types';
import * as tac from '@blockscout/tac-operation-lifecycle-types';
import { AccordionItem, AccordionRoot } from 'toolkit/chakra/accordion';
......@@ -10,13 +10,16 @@ import TacOperationLifecycleAccordionItemTrigger from './TacOperationLifecycleAc
interface Props {
data: tac.OperationDetails['status_history'];
isLoading?: boolean;
type: tac.OperationType;
}
const TacOperationLifecycleAccordion = ({ data, isLoading }: Props) => {
const TacOperationLifecycleAccordion = ({ data, isLoading, type }: Props) => {
const isPending = type === tac.OperationType.PENDING && !isLoading;
return (
<AccordionRoot maxW="800px" display="flex" flexDirection="column" rowGap={ 6 } lazyMount>
{ data.map((item, index) => {
const isLast = index === data.length - 1;
const isLast = index === data.length - 1 && !isPending;
return (
<AccordionItem key={ index } value={ item.type } borderBottomWidth="0px">
<TacOperationLifecycleAccordionItemTrigger
......@@ -33,6 +36,17 @@ const TacOperationLifecycleAccordion = ({ data, isLoading }: Props) => {
</AccordionItem>
);
}) }
{ isPending && (
<AccordionItem value="pending" borderBottomWidth="0px">
<TacOperationLifecycleAccordionItemTrigger
status="pending"
isFirst={ false }
isLast={ true }
isLoading={ isLoading }
isSuccess={ false }
/>
</AccordionItem>
) }
</AccordionRoot>
);
};
......
import { HStack } from '@chakra-ui/react';
import { Box, HStack, Spinner } from '@chakra-ui/react';
import React from 'react';
import type * as tac from '@blockscout/tac-operation-lifecycle-types';
......@@ -9,7 +9,7 @@ import { Skeleton } from 'toolkit/chakra/skeleton';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
status: tac.OperationStage_StageType;
status: tac.OperationStage_StageType | 'pending';
isFirst: boolean;
isLast: boolean;
isLoading?: boolean;
......@@ -17,6 +17,32 @@ interface Props {
}
const TacOperationLifecycleAccordionItemTrigger = ({ status, isFirst, isLast, isSuccess, isLoading }: Props) => {
const content = (() => {
switch (status) {
case 'pending': {
return (
<HStack gap={ 2 }>
<Spinner size="md"/>
<Box color="text.secondary">
Pending
</Box>
</HStack>
);
}
default: {
return (
<HStack gap={ 2 } color={ isSuccess ? 'green.500' : 'red.600' }>
<IconSvg name={ isSuccess ? 'verification-steps/finalized' : 'verification-steps/error' } boxSize={ 5 } isLoading={ isLoading }/>
<Skeleton loading={ isLoading }>
{ STATUS_LABELS[status] }
</Skeleton>
</HStack>
);
}
}
})();
return (
<AccordionItemTrigger
position="relative"
......@@ -47,15 +73,14 @@ const TacOperationLifecycleAccordionItemTrigger = ({ status, isFirst, isLast, is
height: { base: '14px', lg: '6px' },
},
}}
disabled={ isLoading }
noIndicator={ isLoading }
disabled={ isLoading || status === 'pending' }
noIndicator={ isLoading || status === 'pending' }
cursor={ status === 'pending' ? 'default' : 'pointer' }
_disabled={{
opacity: status === 'pending' ? 1 : 'control.disabled',
}}
>
<HStack gap={ 2 } color={ isSuccess ? 'green.500' : 'red.600' }>
<IconSvg name={ isSuccess ? 'verification-steps/finalized' : 'verification-steps/error' } boxSize={ 5 } isLoading={ isLoading }/>
<Skeleton loading={ isLoading }>
{ STATUS_LABELS[status] }
</Skeleton>
</HStack>
{ content }
</AccordionItemTrigger>
);
};
......
......@@ -18,6 +18,7 @@ const TacOperationsListItem = ({ item, isLoading }: Props) => {
<ListItemMobileGrid.Value>
<OperationEntity
id={ item.operation_id }
type={ item.type }
isLoading={ isLoading }
/>
</ListItemMobileGrid.Value>
......
......@@ -19,12 +19,12 @@ const TacOperationsTable = ({ items, isLoading }: Props) => {
<TableRoot minW="950px">
<TableHeaderSticky top={ 68 }>
<TableRow>
<TableColumnHeader w="200px">Status</TableColumnHeader>
<TableColumnHeader w="100%">Operation</TableColumnHeader>
<TableColumnHeader w="200px">
Timestamp
<TimeFormatToggle/>
</TableColumnHeader>
<TableColumnHeader w="200px">Status</TableColumnHeader>
<TableColumnHeader w="250px">Sender</TableColumnHeader>
</TableRow>
</TableHeaderSticky>
......
......@@ -15,10 +15,15 @@ interface Props {
const TacOperationsTableItem = ({ item, isLoading }: Props) => {
return (
<TableRow>
<TableCell verticalAlign="middle">
<TacOperationStatus status={ item.type } isLoading={ isLoading }/>
</TableCell>
<TableCell verticalAlign="middle">
<OperationEntity
id={ item.operation_id }
type={ item.type }
isLoading={ isLoading }
truncation="constant_long"
/>
</TableCell>
<TableCell verticalAlign="middle">
......@@ -28,9 +33,6 @@ const TacOperationsTableItem = ({ item, isLoading }: Props) => {
color="text.secondary"
/>
</TableCell>
<TableCell verticalAlign="middle">
<TacOperationStatus status={ item.type } isLoading={ isLoading }/>
</TableCell>
<TableCell verticalAlign="middle" pr={ 12 }>
{ item.sender ? (
<AddressEntityTacTon
......
import React from 'react';
import * as tac from '@blockscout/tac-operation-lifecycle-types';
import * as tacOperationMock from 'mocks/operations/tac';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';
......@@ -27,3 +29,25 @@ test('base view +@dark-mode +@mobile', async({ render, mockTextAd, mockApiRespon
await component.getByRole('button', { name: 'Executed in TON' }).click();
await expect(component).toHaveScreenshot();
});
test('pending operation', async({ render, mockTextAd, mockApiResponse, mockEnvs }) => {
await mockEnvs(ENVS_MAP.tac);
await mockTextAd();
await mockApiResponse('tac:operation', {
... tacOperationMock.tacOperation,
type: tac.OperationType.PENDING,
}, {
pathParams: { id: tacOperationMock.tacOperation.operation_id },
});
const component = await render(
<TacOperation/>,
{ hooksConfig: {
router: {
query: { id: tacOperationMock.tacOperation.operation_id },
isReady: true,
},
} },
);
await expect(component).toHaveScreenshot();
});
......@@ -44,7 +44,7 @@ const TacOperation = () => {
) : null;
const titleSecondRow = (
<OperationEntity id={ id } noLink variant="subheading"/>
<OperationEntity id={ id } noLink variant="subheading" type={ query.data?.type }/>
);
return (
......
......@@ -219,7 +219,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
case 'tac_operation': {
return (
<OperationEntity.Container>
<OperationEntity.Icon/>
<OperationEntity.Icon type={ data.tac_operation.type }/>
<OperationEntity.Link
isLoading={ isLoading }
id={ data.tac_operation.operation_id }
......
......@@ -331,7 +331,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
<>
<TableCell colSpan={ 2 } fontSize="sm">
<OperationEntity.Container>
<OperationEntity.Icon/>
<OperationEntity.Icon type={ data.tac_operation.type }/>
<OperationEntity.Link
isLoading={ isLoading }
id={ data.tac_operation.operation_id }
......
import { chakra } from '@chakra-ui/react';
import { Spinner, chakra } from '@chakra-ui/react';
import React from 'react';
import * as tac from '@blockscout/tac-operation-lifecycle-types';
import { route } from 'nextjs-routes';
import * as EntityBase from 'ui/shared/entities/base/components';
......@@ -22,14 +24,40 @@ const Link = chakra((props: LinkProps) => {
);
});
const Icon = (props: EntityBase.IconBaseProps) => {
type IconProps = EntityBase.IconBaseProps & Pick<EntityProps, 'type'>;
const Icon = (props: IconProps) => {
switch (props.type) {
case tac.OperationType.PENDING: {
return <Spinner size="md" marginRight={ props.marginRight ?? '8px' }/>;
}
default: {
const color = (() => {
switch (props.type) {
case tac.OperationType.ERROR:
case tac.OperationType.ROLLBACK: {
return 'red.500';
}
case tac.OperationType.TAC_TON:
case tac.OperationType.TON_TAC_TON:
case tac.OperationType.TON_TAC: {
return 'green.500';
}
default:
return;
}
})();
return (
<EntityBase.Icon
{ ...props }
color={ color }
name={ props.name ?? 'operation_slim' }
borderRadius="none"
/>
);
}
}
};
type ContentProps = Omit<EntityBase.ContentBaseProps, 'text'> & Pick<EntityProps, 'id'>;
......@@ -58,6 +86,7 @@ const Container = EntityBase.Container;
export interface EntityProps extends EntityBase.EntityBaseProps {
id: string;
type: tac.OperationType | undefined;
}
const OperationEntity = (props: EntityProps) => {
......
......@@ -10,7 +10,7 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TacOperationTag from 'ui/shared/TacOperationTag';
const SearchBarSuggestTacOperation = ({ data, isMobile }: ItemsProps<SearchResultTacOperation>) => {
const icon = <OperationEntity.Icon/>;
const icon = <OperationEntity.Icon type={ data.tac_operation.type }/>;
const hash = (
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 } mr={ 2 }>
<HashStringShortenDynamic hash={ data.tac_operation.operation_id } noTooltip/>
......
......@@ -43,6 +43,7 @@ const TxDetailsTacOperation = ({ tacOperations, isLoading, txHash }: Props) => {
<HStack key={ tacOperation.operation_id } gap={ 3 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
<OperationEntity
id={ tacOperation.operation_id }
type={ tacOperation.type }
isLoading={ isLoading }
/>
{ tags.length > 0 && (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment