fix(explorer): add party to consolidated search (#1588)
* fix(explorer): add party to consolidated search * chore(explorer): add missing tests for detect-search.spec.ts
This commit is contained in:
parent
09476eea38
commit
620bf1bab4
@ -14,7 +14,12 @@ jest.mock('../search', () => ({
|
|||||||
|
|
||||||
const renderComponent = () => (
|
const renderComponent = () => (
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Header />
|
<Header
|
||||||
|
theme="dark"
|
||||||
|
toggleTheme={jest.fn()}
|
||||||
|
menuOpen={false}
|
||||||
|
setMenuOpen={jest.fn()}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
231
apps/explorer/src/app/components/search/detect-search.spec.ts
Normal file
231
apps/explorer/src/app/components/search/detect-search.spec.ts
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import {
|
||||||
|
detectTypeByFetching,
|
||||||
|
detectTypeFromQuery,
|
||||||
|
getSearchType,
|
||||||
|
isBlock,
|
||||||
|
isHexadecimal,
|
||||||
|
isNetworkParty,
|
||||||
|
isNonHex,
|
||||||
|
SearchTypes,
|
||||||
|
toHex,
|
||||||
|
toNonHex,
|
||||||
|
} from './detect-search';
|
||||||
|
import { DATA_SOURCES } from '../../config';
|
||||||
|
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
|
||||||
|
describe('Detect Search', () => {
|
||||||
|
it("should detect that it's a hexadecimal", () => {
|
||||||
|
const expected = true;
|
||||||
|
const testString =
|
||||||
|
'0x073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
|
||||||
|
const actual = isHexadecimal(testString);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect that it's not hexadecimal", () => {
|
||||||
|
const expected = true;
|
||||||
|
const testString =
|
||||||
|
'073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
|
||||||
|
const actual = isNonHex(testString);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect that it's a network party", () => {
|
||||||
|
const expected = true;
|
||||||
|
const testString = 'network';
|
||||||
|
const actual = isNetworkParty(testString);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect that it's a block", () => {
|
||||||
|
const expected = true;
|
||||||
|
const testString = '3188';
|
||||||
|
const actual = isBlock(testString);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert from non-hex to hex', () => {
|
||||||
|
const expected = '0x123';
|
||||||
|
const testString = '123';
|
||||||
|
const actual = toHex(testString);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert from hex to non-hex', () => {
|
||||||
|
const expected = '123';
|
||||||
|
const testString = '0x123';
|
||||||
|
const actual = toNonHex(testString);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect type client side from query if it's a hexadecimal", () => {
|
||||||
|
const expected = [SearchTypes.Party, SearchTypes.Transaction];
|
||||||
|
const testString =
|
||||||
|
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||||
|
const actual = detectTypeFromQuery(testString);
|
||||||
|
expect(actual).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect type client side from query if it's a non hex", () => {
|
||||||
|
const expected = [SearchTypes.Party, SearchTypes.Transaction];
|
||||||
|
const testString =
|
||||||
|
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||||
|
const actual = detectTypeFromQuery(testString);
|
||||||
|
expect(actual).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect type client side from query if it's a network party", () => {
|
||||||
|
const expected = [SearchTypes.Party];
|
||||||
|
const testString = 'network';
|
||||||
|
const actual = detectTypeFromQuery(testString);
|
||||||
|
expect(actual).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect type client side from query if it's a block (number)", () => {
|
||||||
|
const expected = [SearchTypes.Block];
|
||||||
|
const testString = '23432';
|
||||||
|
const actual = detectTypeFromQuery(testString);
|
||||||
|
expect(actual).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detectTypeByFetching should call fetch with hex query it's a transaction", async () => {
|
||||||
|
const query = 'abc';
|
||||||
|
const type = SearchTypes.Transaction;
|
||||||
|
// @ts-ignore issue related to polyfill
|
||||||
|
fetch.mockImplementation(
|
||||||
|
jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
result: {
|
||||||
|
tx: query,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const result = await detectTypeByFetching(query, type);
|
||||||
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
|
`${DATA_SOURCES.tendermintUrl}/tx?hash=0x${query}`
|
||||||
|
);
|
||||||
|
expect(result).toBe(type);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detectTypeByFetching should call fetch with non-hex query it's a party", async () => {
|
||||||
|
const query = 'abc';
|
||||||
|
const type = SearchTypes.Party;
|
||||||
|
// @ts-ignore issue related to polyfill
|
||||||
|
fetch.mockImplementation(
|
||||||
|
jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
result: {
|
||||||
|
txs: [query],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const result = await detectTypeByFetching(query, type);
|
||||||
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
|
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${query}'"`
|
||||||
|
);
|
||||||
|
expect(result).toBe(type);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detectTypeByFetching should return undefined if no matches', async () => {
|
||||||
|
const query = 'abc';
|
||||||
|
const type = SearchTypes.Party;
|
||||||
|
// @ts-ignore issue related to polyfill
|
||||||
|
fetch.mockImplementation(
|
||||||
|
jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const result = await detectTypeByFetching(query, type);
|
||||||
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
|
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${query}'"`
|
||||||
|
);
|
||||||
|
expect(result).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSearchType should return party from fetch response', async () => {
|
||||||
|
const query =
|
||||||
|
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||||
|
const expected = SearchTypes.Party;
|
||||||
|
// @ts-ignore issue related to polyfill
|
||||||
|
fetch.mockImplementation(
|
||||||
|
jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
result: {
|
||||||
|
txs: [query],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const result = await getSearchType(query);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSearchType should return party from transaction response', async () => {
|
||||||
|
const query =
|
||||||
|
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||||
|
const expected = SearchTypes.Transaction;
|
||||||
|
// @ts-ignore issue related to polyfill
|
||||||
|
fetch.mockImplementation(
|
||||||
|
jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
result: {
|
||||||
|
tx: query,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const result = await getSearchType(query);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSearchType should return undefined from transaction response', async () => {
|
||||||
|
const query =
|
||||||
|
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||||
|
const expected = undefined;
|
||||||
|
// @ts-ignore issue related to polyfill
|
||||||
|
fetch.mockImplementation(
|
||||||
|
jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const result = await getSearchType(query);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSearchType should return block if query is number', async () => {
|
||||||
|
const query = '123';
|
||||||
|
const expected = SearchTypes.Block;
|
||||||
|
const result = await getSearchType(query);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSearchType should return party if query is network', async () => {
|
||||||
|
const query = 'network';
|
||||||
|
const expected = SearchTypes.Party;
|
||||||
|
const result = await getSearchType(query);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
104
apps/explorer/src/app/components/search/detect-search.ts
Normal file
104
apps/explorer/src/app/components/search/detect-search.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { DATA_SOURCES } from '../../config';
|
||||||
|
|
||||||
|
export enum SearchTypes {
|
||||||
|
Transaction = 'transaction',
|
||||||
|
Party = 'party',
|
||||||
|
Block = 'block',
|
||||||
|
Order = 'order',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TX_LENGTH = 64;
|
||||||
|
|
||||||
|
export const isHexadecimal = (search: string) =>
|
||||||
|
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
|
||||||
|
|
||||||
|
export const isNonHex = (search: string) =>
|
||||||
|
!search.startsWith('0x') && search.length === TX_LENGTH;
|
||||||
|
|
||||||
|
export const isBlock = (search: string) => !Number.isNaN(Number(search));
|
||||||
|
|
||||||
|
export const isNetworkParty = (search: string) => search === 'network';
|
||||||
|
|
||||||
|
export const toHex = (query: string) =>
|
||||||
|
isHexadecimal(query) ? query : `0x${query}`;
|
||||||
|
|
||||||
|
export const toNonHex = (query: string) =>
|
||||||
|
isNonHex(query) ? query : `${query.replace('0x', '')}`;
|
||||||
|
|
||||||
|
export const detectTypeFromQuery = (
|
||||||
|
query: string
|
||||||
|
): SearchTypes[] | undefined => {
|
||||||
|
const i = query.toLowerCase();
|
||||||
|
|
||||||
|
if (isHexadecimal(i) || isNonHex(i)) {
|
||||||
|
return [SearchTypes.Party, SearchTypes.Transaction];
|
||||||
|
} else if (isNetworkParty(i)) {
|
||||||
|
return [SearchTypes.Party];
|
||||||
|
} else if (isBlock(i)) {
|
||||||
|
return [SearchTypes.Block];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const detectTypeByFetching = async (
|
||||||
|
query: string,
|
||||||
|
type: SearchTypes
|
||||||
|
): Promise<SearchTypes | undefined> => {
|
||||||
|
const TYPES = [SearchTypes.Party, SearchTypes.Transaction];
|
||||||
|
|
||||||
|
if (!TYPES.includes(type)) {
|
||||||
|
throw new Error('Search type provided not recognised');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === SearchTypes.Transaction) {
|
||||||
|
const hash = toHex(query);
|
||||||
|
const request = await fetch(
|
||||||
|
`${DATA_SOURCES.tendermintUrl}/tx?hash=${hash}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (request?.ok) {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
if (body?.result?.tx) {
|
||||||
|
return SearchTypes.Transaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === SearchTypes.Party) {
|
||||||
|
const party = toNonHex(query);
|
||||||
|
const request = await fetch(
|
||||||
|
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${party}'"`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (request.ok) {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
if (body?.result?.txs?.length) {
|
||||||
|
return SearchTypes.Party;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSearchType = async (
|
||||||
|
query: string
|
||||||
|
): Promise<SearchTypes | undefined> => {
|
||||||
|
const searchTypes = detectTypeFromQuery(query);
|
||||||
|
const hasResults = searchTypes?.length;
|
||||||
|
|
||||||
|
if (hasResults) {
|
||||||
|
if (hasResults > 1) {
|
||||||
|
const promises = searchTypes.map((type) =>
|
||||||
|
detectTypeByFetching(query, type)
|
||||||
|
);
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
return results.find((type) => type !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchTypes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
@ -2,14 +2,23 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|||||||
import { Search } from './search';
|
import { Search } from './search';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Routes } from '../../routes/route-names';
|
import { Routes } from '../../routes/route-names';
|
||||||
|
import { SearchTypes, getSearchType } from './detect-search';
|
||||||
|
|
||||||
const mockedNavigate = jest.fn();
|
const mockedNavigate = jest.fn();
|
||||||
|
const mockGetSearchType = getSearchType as jest.MockedFunction<
|
||||||
|
typeof getSearchType
|
||||||
|
>;
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...jest.requireActual('react-router-dom'),
|
||||||
useNavigate: () => mockedNavigate,
|
useNavigate: () => mockedNavigate,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('./detect-search', () => ({
|
||||||
|
...jest.requireActual('./detect-search'),
|
||||||
|
getSearchType: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedNavigate.mockClear();
|
mockedNavigate.mockClear();
|
||||||
});
|
});
|
||||||
@ -39,9 +48,10 @@ describe('Search', () => {
|
|||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
|
|
||||||
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
||||||
"Something doesn't look right"
|
'Transaction type is not recognised'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render error if no input is given', async () => {
|
it('should render error if no input is given', async () => {
|
||||||
render(renderComponent());
|
render(renderComponent());
|
||||||
const { button } = getInputs();
|
const { button } = getInputs();
|
||||||
@ -49,47 +59,14 @@ describe('Search', () => {
|
|||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
|
|
||||||
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
||||||
'Search required'
|
'Search query required'
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render error if transaction is not hex', async () => {
|
|
||||||
render(renderComponent());
|
|
||||||
const { button, input } = getInputs();
|
|
||||||
fireEvent.change(input, {
|
|
||||||
target: {
|
|
||||||
value:
|
|
||||||
'0x123456789012345678901234567890123456789012345678901234567890123Q',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.click(button);
|
|
||||||
|
|
||||||
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
|
||||||
'Transaction is not hexadecimal'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render error if transaction is not hex and does not have leading 0x', async () => {
|
|
||||||
render(renderComponent());
|
|
||||||
const { button, input } = getInputs();
|
|
||||||
fireEvent.change(input, {
|
|
||||||
target: {
|
|
||||||
value:
|
|
||||||
'123456789012345678901234567890123456789012345678901234567890123Q',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.click(button);
|
|
||||||
|
|
||||||
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
|
||||||
'Transaction is not hexadecimal'
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should redirect to transactions page', async () => {
|
it('should redirect to transactions page', async () => {
|
||||||
render(renderComponent());
|
render(renderComponent());
|
||||||
const { button, input } = getInputs();
|
const { button, input } = getInputs();
|
||||||
|
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
|
||||||
fireEvent.change(input, {
|
fireEvent.change(input, {
|
||||||
target: {
|
target: {
|
||||||
value:
|
value:
|
||||||
@ -108,6 +85,7 @@ describe('Search', () => {
|
|||||||
it('should redirect to transactions page without proceeding 0x', async () => {
|
it('should redirect to transactions page without proceeding 0x', async () => {
|
||||||
render(renderComponent());
|
render(renderComponent());
|
||||||
const { button, input } = getInputs();
|
const { button, input } = getInputs();
|
||||||
|
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
|
||||||
fireEvent.change(input, {
|
fireEvent.change(input, {
|
||||||
target: {
|
target: {
|
||||||
value:
|
value:
|
||||||
@ -123,9 +101,48 @@ describe('Search', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should redirect to parties page', async () => {
|
||||||
|
render(renderComponent());
|
||||||
|
const { button, input } = getInputs();
|
||||||
|
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: {
|
||||||
|
value:
|
||||||
|
'0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockedNavigate).toBeCalledWith(
|
||||||
|
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to parties page without proceeding 0x', async () => {
|
||||||
|
render(renderComponent());
|
||||||
|
const { button, input } = getInputs();
|
||||||
|
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: {
|
||||||
|
value:
|
||||||
|
'1234567890123456789012345678901234567890123456789012345678901234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockedNavigate).toBeCalledWith(
|
||||||
|
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should redirect to blocks page if passed a number', async () => {
|
it('should redirect to blocks page if passed a number', async () => {
|
||||||
render(renderComponent());
|
render(renderComponent());
|
||||||
const { button, input } = getInputs();
|
const { button, input } = getInputs();
|
||||||
|
mockGetSearchType.mockResolvedValue(SearchTypes.Block);
|
||||||
fireEvent.change(input, {
|
fireEvent.change(input, {
|
||||||
target: {
|
target: {
|
||||||
value: '123',
|
value: '123',
|
||||||
|
@ -1,55 +1,57 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { Input, InputError, Button } from '@vegaprotocol/ui-toolkit';
|
import { Button, Input, InputError } from '@vegaprotocol/ui-toolkit';
|
||||||
import React from 'react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getSearchType, SearchTypes, toHex } from './detect-search';
|
||||||
import { Routes } from '../../routes/route-names';
|
import { Routes } from '../../routes/route-names';
|
||||||
|
|
||||||
const TX_LENGTH = 64;
|
|
||||||
|
|
||||||
interface FormFields {
|
interface FormFields {
|
||||||
search: string;
|
search: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPrependedTransaction = (search: string) =>
|
|
||||||
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
|
|
||||||
|
|
||||||
const isTransaction = (search: string) =>
|
|
||||||
!search.startsWith('0x') && search.length === TX_LENGTH;
|
|
||||||
|
|
||||||
const isBlock = (search: string) => !Number.isNaN(Number(search));
|
|
||||||
|
|
||||||
export const Search = () => {
|
export const Search = () => {
|
||||||
const { register, handleSubmit } = useForm<FormFields>();
|
const { register, handleSubmit } = useForm<FormFields>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [error, setError] = React.useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const onSubmit = React.useCallback(
|
|
||||||
(fields: FormFields) => {
|
const onSubmit = useCallback(
|
||||||
|
async (fields: FormFields) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const search = fields.search;
|
const query = fields.search;
|
||||||
if (!search) {
|
|
||||||
setError(new Error(t('Search required')));
|
if (!query) {
|
||||||
} else if (isPrependedTransaction(search)) {
|
setError(new Error(t('Search query required')));
|
||||||
if (Number.isNaN(Number(search))) {
|
|
||||||
setError(new Error(t('Transaction is not hexadecimal')));
|
|
||||||
} else {
|
} else {
|
||||||
navigate(`${Routes.TX}/${search}`);
|
const result = await getSearchType(query);
|
||||||
|
const urlAsHex = toHex(query);
|
||||||
|
const unrecognisedError = new Error(
|
||||||
|
t('Transaction type is not recognised')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
switch (result) {
|
||||||
|
case SearchTypes.Party:
|
||||||
|
navigate(`${Routes.PARTIES}/${urlAsHex}`);
|
||||||
|
break;
|
||||||
|
case SearchTypes.Transaction:
|
||||||
|
navigate(`${Routes.TX}/${urlAsHex}`);
|
||||||
|
break;
|
||||||
|
case SearchTypes.Block:
|
||||||
|
navigate(`${Routes.BLOCKS}/${Number(query)}`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setError(unrecognisedError);
|
||||||
}
|
}
|
||||||
} else if (isTransaction(search)) {
|
|
||||||
if (Number.isNaN(Number(`0x${search}`))) {
|
|
||||||
setError(new Error(t('Transaction is not hexadecimal')));
|
|
||||||
} else {
|
|
||||||
navigate(`${Routes.TX}/0x${search}`);
|
|
||||||
}
|
}
|
||||||
} else if (isBlock(search)) {
|
|
||||||
navigate(`${Routes.BLOCKS}/${Number(search)}`);
|
setError(unrecognisedError);
|
||||||
} else {
|
|
||||||
setError(new Error(t("Something doesn't look right")));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
@ -66,7 +68,7 @@ export const Search = () => {
|
|||||||
className="text-white"
|
className="text-white"
|
||||||
hasError={Boolean(error?.message)}
|
hasError={Boolean(error?.message)}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t('Enter block number or transaction hash')}
|
placeholder={t('Enter block number, party id or transaction hash')}
|
||||||
/>
|
/>
|
||||||
{error?.message && (
|
{error?.message && (
|
||||||
<div className="absolute top-[100%] flex-1 w-full">
|
<div className="absolute top-[100%] flex-1 w-full">
|
||||||
|
Loading…
Reference in New Issue
Block a user