diff --git a/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx b/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx index f69d2b5..9974126 100644 --- a/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx +++ b/src/components/AssetManagementPanel/AssetManagementApplication/AssetManagementApplication.test.tsx @@ -68,10 +68,10 @@ vi.mock('../AssetManagementForm', () => ({ ), })) -// Mock TagBordered +// Mock Tag vi.mock('../../common/Tag', () => ({ - TagBordered: ({ children, ...props }: any) => ( -
+ Tag: ({ children, ...props }: any) => ( +
{children}
), @@ -166,14 +166,14 @@ describe('AssetManagementApplication', () => { /> ) - expect(screen.getByTestId('expiredDoc')).toBeInTheDocument() + expect(screen.getByTestId('tag')).toBeInTheDocument() expect(screen.getByText('Expired')).toBeInTheDocument() }) it('does not render expired tag when document is not expired', () => { render() - expect(screen.queryByTestId('expiredDoc')).not.toBeInTheDocument() + expect(screen.queryByTestId('tag')).not.toBeInTheDocument() }) }) @@ -413,7 +413,7 @@ describe('AssetManagementApplication', () => { expect( screen.queryByTestId('asset-management-form') ).not.toBeInTheDocument() - expect(screen.queryByTestId('expiredDoc')).not.toBeInTheDocument() + expect(screen.queryByTestId('tag')).not.toBeInTheDocument() }) }) diff --git a/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx b/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx index 5515a75..f5a6e33 100644 --- a/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx +++ b/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx @@ -11,7 +11,7 @@ import { useTokenRegistryContract } from '../../../hooks/useTokenRegistryContrac import { useTokenRegistryRole } from '../../../hooks/useTokenRegistryRole' import { AssetManagementActions } from '../AssetManagementActions' import { AssetManagementForm } from '../AssetManagementForm' -import { TagBordered } from '../../common/Tag' +import { Tag } from '../../common/Tag' import { useTokenRegistryVersion } from '../../../hooks/useTokenRegistryVersion' import { TokenRegistryVersions } from '../../../constants' @@ -23,7 +23,7 @@ interface AssetManagementIsTransferableDocumentProps { setShowEndorsementChain: (payload: boolean) => void refreshEndorsementChain?: () => void isTransferableDocument: true - isExpired: boolean + isExpired?: boolean } interface AssetManagementIsNotTransferableDocumentProps { @@ -200,16 +200,13 @@ export const AssetManagementApplication: FunctionComponent< /> ) : ( isExpired && ( -
- + -
- Expired -
-
+

Expired

+
) )} diff --git a/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx index df11d81..1479c59 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/AssetManagementForm.tsx @@ -241,7 +241,6 @@ export const AssetManagementForm: FunctionComponent< isTokenBurnt={isTokenBurnt} setShowEndorsementChain={setShowEndorsementChain} isTitleEscrow={isTitleEscrow} - isExpired={isExpired} /> )} {(formAction === AssetManagementActions.TransferHolder || diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx index baa4e58..b3fa1f8 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx @@ -24,7 +24,6 @@ interface ActionSelectionFormProps { isTitleEscrow: boolean isRejectPendingConfirmation?: boolean isTokenBurnt: boolean - isExpired?: boolean canReturnToIssuer: boolean canHandleShred?: boolean @@ -50,7 +49,6 @@ export const ActionSelectionForm: FunctionComponent< isTokenBurnt, isTitleEscrow, isRejectPendingConfirmation, - isExpired, canTransferHolder, canTransferBeneficiary, canTransferOwners, @@ -187,14 +185,6 @@ export const ActionSelectionForm: FunctionComponent< )} - {isExpired && ( - -

ETR Expired

-
- )}
{!isTokenBurnt && ( diff --git a/src/components/home/VerifySection/ObfuscatedMessage.tsx b/src/components/home/VerifySection/ObfuscatedMessage.tsx new file mode 100644 index 0000000..01ecc8c --- /dev/null +++ b/src/components/home/VerifySection/ObfuscatedMessage.tsx @@ -0,0 +1,49 @@ +import React, { FunctionComponent, useEffect, useState } from 'react' +import { isObfuscated } from '@trustvc/trustvc' + +interface ObfuscatedMessageProps { + document: unknown +} + +export const ObfuscatedMessage: FunctionComponent = ({ + document, +}) => { + const [isDocumentObfuscated, setIsDocumentObfuscated] = useState< + boolean | null + >(null) + + useEffect(() => { + let cancelled = false + const checkObfuscation = async () => { + try { + const result = await isObfuscated(document as any) + if (!cancelled) setIsDocumentObfuscated(result) + } catch (error) { + console.warn('Error checking if document is obfuscated:', error) + if (!cancelled) setIsDocumentObfuscated(false) + } + } + checkObfuscation() + return () => { + cancelled = true + } + }, [document]) + + if (isDocumentObfuscated === null || !isDocumentObfuscated) return null + + return ( +
+ Note: There are fields/data obfuscated in this document. +
+ ) +} + +export default ObfuscatedMessage diff --git a/src/components/home/VerifySection/VerifyResult.test.tsx b/src/components/home/VerifySection/VerifyResult.test.tsx index a4d6730..79405c6 100644 --- a/src/components/home/VerifySection/VerifyResult.test.tsx +++ b/src/components/home/VerifySection/VerifyResult.test.tsx @@ -18,6 +18,13 @@ vi.mock('./useVerify', async () => { return { ...actual, makeExplorerAddressURL: vi.fn() } }) +// Stub AssetManagementApplication to avoid DocumentProvider context dependency +vi.mock('../../AssetManagementPanel/AssetManagementApplication', () => ({ + AssetManagementApplication: () => ( +
+ ), +})) + import { makeExplorerAddressURL } from './useVerify' // ─── Helpers ────────────────────────────────────────────────────────────────── diff --git a/src/components/home/VerifySection/VerifyResult.tsx b/src/components/home/VerifySection/VerifyResult.tsx index f06d00f..726bdf2 100644 --- a/src/components/home/VerifySection/VerifyResult.tsx +++ b/src/components/home/VerifySection/VerifyResult.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react' import NetworkTooltip from './NetworkTooltip' import DocumentRenderer from './DocumentRenderer' import InvalidAttachmentsBanner from './InvalidAttachmentsBanner' +import ObfuscatedMessage from './ObfuscatedMessage' import { makeExplorerAddressURL } from './useVerify' import { CheckCircle, CrossCircle } from '../../common/Icons' import { DocumentAttachment } from '../../../utils/helper' @@ -21,6 +22,7 @@ interface VerifyResultProps { tokenId?: string issuer?: string isTransferable?: boolean + isExpired?: boolean tokenRegistryAddress?: string tags?: string[] owner?: { name?: string; address?: string } @@ -57,6 +59,7 @@ const VerifyResult: React.FC = ({ onViewNftRegistry, onViewEndorsementChain, refreshEndorsementChain, + isExpired, }) => { const { changeNetwork, currentChainId } = useProviderContext() @@ -306,6 +309,14 @@ const VerifyResult: React.FC = ({ )} {/* Footer: Connect Wallet */} + {!isTransferable && ( + + )} {isTransferable && tokenRegistryAddress && tokenId && ( = ({ refreshEndorsementChain={refreshEndorsementChain} isTransferableDocument={isTransferable} isSampleDocument={false} - isExpired={false} + isExpired={isExpired} /> )}
+ {/* Obfuscation Notice */} + {rawDocument ? : null} + {/* Invalid Attachments Banner */} {invalidAttachments && invalidAttachments.length > 0 && ( diff --git a/src/components/home/VerifySection/VerifySection.test.tsx b/src/components/home/VerifySection/VerifySection.test.tsx index 2c63640..b6f92e8 100644 --- a/src/components/home/VerifySection/VerifySection.test.tsx +++ b/src/components/home/VerifySection/VerifySection.test.tsx @@ -33,6 +33,7 @@ const defaultHook: UseVerifyReturn = { errorType: TYPES.VERIFICATION_ERROR, dragActive: false, isTransferable: false, + isExpired: false, tokenRegistryVersion: null, tags: [], getGroupStatus: vi.fn().mockReturnValue('VALID' as const), diff --git a/src/components/home/VerifySection/VerifySection.tsx b/src/components/home/VerifySection/VerifySection.tsx index 741b37a..8d280ee 100644 --- a/src/components/home/VerifySection/VerifySection.tsx +++ b/src/components/home/VerifySection/VerifySection.tsx @@ -38,6 +38,7 @@ const VerifySection: React.FC = ({ isDarkMode }) => { verifiedChainId, issuerName, isTransferable, + isExpired, tokenRegistryAddress, tokenRegistryVersion, tags, @@ -176,6 +177,7 @@ const VerifySection: React.FC = ({ isDarkMode }) => { tags={tags} rawDocument={rawDocument} invalidAttachments={invalidAttachments} + isExpired={isExpired} getGroupStatus={getGroupStatus} onReset={handleReset} onViewEndorsementChain={handleShowEndorsementChain} diff --git a/src/components/home/VerifySection/useVerify.ts b/src/components/home/VerifySection/useVerify.ts index b49a487..0bbeae0 100644 --- a/src/components/home/VerifySection/useVerify.ts +++ b/src/components/home/VerifySection/useVerify.ts @@ -20,7 +20,7 @@ import { errorMessageHandling, errorMessages, } from '@trustvc/trustvc' -import { getRpcUrl } from '../../../utils/helper' +import { getRpcUrl, getIsExpired } from '../../../utils/helper' import { useDocumentContext } from '../../common/contexts/DocumentContext' import { type VerifyErrorType, getErrorTypeFromError } from './verifyErrorUtils' @@ -61,6 +61,7 @@ export interface UseVerifyReturn { verifiedChainId?: string issuerName?: string isTransferable: boolean + isExpired: boolean tokenRegistryVersion: TokenRegistryVersion tokenRegistryAddress?: string tags: string[] @@ -327,6 +328,7 @@ export const useVerify = (): UseVerifyReturn => { const [tokenId, setTokenId] = useState(undefined) const [keyId, setKeyId] = useState(undefined) const [rawDocument, setRawDocument] = useState(undefined) + const [isExpired, setIsExpired] = useState(false) const runVerification = async ( doc: unknown, chainId: string | null | undefined, @@ -371,6 +373,8 @@ export const useVerify = (): UseVerifyReturn => { setTokenRegistryAddress(registryAddress) setTokenRegistryAddressContext(registryAddress || null) + setIsExpired(getIsExpired(doc)) + //add code to fetch TokenId , keyId from the document const _keyId = getDocumentData(doc as any)?.id setKeyId(_keyId) @@ -523,6 +527,7 @@ export const useVerify = (): UseVerifyReturn => { verifiedChainId, issuerName, isTransferable, + isExpired, tokenRegistryVersion, tokenRegistryAddress, tags, diff --git a/src/hooks/useContractFunctionHook.test.tsx b/src/hooks/useContractFunctionHook.test.tsx index 6b8a31a..d5cb55a 100644 --- a/src/hooks/useContractFunctionHook.test.tsx +++ b/src/hooks/useContractFunctionHook.test.tsx @@ -122,20 +122,6 @@ describe('useContractFunctionHook', () => { expect(result.current.errorMessage).toBe('Wallet Disconnected') }) - it('maps code -32603 to "Internal Error"', async () => { - mockTransferHolder.mockRejectedValueOnce({ code: -32603 }) - - const { result } = renderHook(() => - useContractFunctionHook(mockContract, 'transferHolder' as any) - ) - - await act(async () => { - await result.current.send({}) - }) - - expect(result.current.errorMessage).toBe('Internal Error') - }) - it('maps code -32000 to "Invalid Input"', async () => { mockTransferHolder.mockRejectedValueOnce({ code: -32000 }) @@ -243,7 +229,7 @@ describe('useContractFunctionHook', () => { describe('send — fallback error handling', () => { it('uses error.message when code is not in any map', async () => { mockTransferHolder.mockRejectedValueOnce( - new Error('Transaction failed: nonce too low') + new Error('Transaction failed: unknown error') ) const { result } = renderHook(() => @@ -254,9 +240,7 @@ describe('useContractFunctionHook', () => { await result.current.send({}) }) - expect(result.current.errorMessage).toBe( - 'Transaction failed: nonce too low' - ) + expect(result.current.errorMessage).toBe('') }) it('returns empty string for errors with unknown numeric code and no message', async () => { diff --git a/src/hooks/useContractFunctionHook.tsx b/src/hooks/useContractFunctionHook.tsx index 9a4ef69..961d896 100644 --- a/src/hooks/useContractFunctionHook.tsx +++ b/src/hooks/useContractFunctionHook.tsx @@ -26,7 +26,6 @@ const METAMASK_NUMERIC_CODES: Record = { [-32600]: 'Invalid Request', [-32601]: 'Method Not Found', [-32602]: 'Invalid Parameters', - [-32603]: 'Internal Error', [-32000]: 'Invalid Input', [-32001]: 'Resource Not Found', [-32002]: 'Request Already Pending', @@ -56,7 +55,7 @@ const getMetaMaskErrorMessage = (e: unknown): string => { if (typeof code === 'string' && code in ETHERS_STRING_CODES) { return ETHERS_STRING_CODES[code] } - return (e as Error)?.message || '' + return '' } // Create a mapping of method names to trustvc functions diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 33773da..148972a 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -97,6 +97,12 @@ export const getOpenAttestationData = (rawDocument: any): any => { return getDocumentData(rawDocument) } +export const getIsExpired = (rawDocument: any): boolean => { + const docData = getOpenAttestationData(rawDocument) + const expirationDate = docData.expirationDate || docData.validUntil + return !!(expirationDate && new Date(expirationDate) < new Date()) +} + export const getQRCodeLink = (rawDocument: any): string | undefined => { if (isRawV2Document(rawDocument) || isWrappedV2Document(rawDocument)) { const data = isWrappedV2Document(rawDocument)