Commit c9b891a3 authored by tom's avatar tom

pass form data to API and display result

parent a0b0cd76
...@@ -240,7 +240,7 @@ export const RESOURCES = { ...@@ -240,7 +240,7 @@ export const RESOURCES = {
filterFields: [ 'name' as const, 'only_active' as const ], filterFields: [ 'name' as const, 'only_active' as const ],
}, },
// METADATA SERVICE // METADATA SERVICE & PUBLIC TAGS
address_metadata_info: { address_metadata_info: {
path: '/api/v1/metadata', path: '/api/v1/metadata',
endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint,
...@@ -256,6 +256,12 @@ export const RESOURCES = { ...@@ -256,6 +256,12 @@ export const RESOURCES = {
endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint,
basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath, basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath,
}, },
public_tag_application: {
path: '/api/v1/chains/:chainId/metadata-submissions/tag',
pathParams: [ 'chainId' as const ],
endpoint: getFeaturePayload(config.features.publicTagsSubmission)?.api.endpoint,
basePath: getFeaturePayload(config.features.publicTagsSubmission)?.api.basePath,
},
// VISUALIZATION // VISUALIZATION
visualize_sol2uml: { visualize_sol2uml: {
......
...@@ -7,7 +7,10 @@ import { useForm, FormProvider } from 'react-hook-form'; ...@@ -7,7 +7,10 @@ import { useForm, FormProvider } from 'react-hook-form';
import type { FormFields, FormSubmitResult } from './types'; import type { FormFields, FormSubmitResult } from './types';
import type { PublicTagTypesResponse } from 'types/api/addressMetadata'; import type { PublicTagTypesResponse } from 'types/api/addressMetadata';
import delay from 'lib/delay'; import appConfig from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch';
import getErrorObj from 'lib/errors/getErrorObj';
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha'; import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha';
import Hint from 'ui/shared/Hint'; import Hint from 'ui/shared/Hint';
...@@ -20,7 +23,7 @@ import PublicTagsSubmitFieldRequesterEmail from './fields/PublicTagsSubmitFieldR ...@@ -20,7 +23,7 @@ import PublicTagsSubmitFieldRequesterEmail from './fields/PublicTagsSubmitFieldR
import PublicTagsSubmitFieldRequesterName from './fields/PublicTagsSubmitFieldRequesterName'; import PublicTagsSubmitFieldRequesterName from './fields/PublicTagsSubmitFieldRequesterName';
import PublicTagsSubmitFieldTags from './fields/PublicTagsSubmitFieldTags'; import PublicTagsSubmitFieldTags from './fields/PublicTagsSubmitFieldTags';
import * as mocks from './mocks'; import * as mocks from './mocks';
import { getDefaultValuesFromQuery } from './utils'; import { convertFormDataToRequestsBody, getDefaultValuesFromQuery } from './utils';
interface Props { interface Props {
config: PublicTagTypesResponse | undefined; config: PublicTagTypesResponse | undefined;
...@@ -30,6 +33,7 @@ interface Props { ...@@ -30,6 +33,7 @@ interface Props {
const PublicTagsSubmitForm = ({ config, onSubmitResult }: Props) => { const PublicTagsSubmitForm = ({ config, onSubmitResult }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const router = useRouter(); const router = useRouter();
const apiFetch = useApiFetch();
const formApi = useForm<FormFields>({ const formApi = useForm<FormFields>({
mode: 'onBlur', mode: 'onBlur',
...@@ -53,14 +57,28 @@ const PublicTagsSubmitForm = ({ config, onSubmitResult }: Props) => { ...@@ -53,14 +57,28 @@ const PublicTagsSubmitForm = ({ config, onSubmitResult }: Props) => {
}, [ onSubmitResult ]); }, [ onSubmitResult ]);
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => { const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
// eslint-disable-next-line no-console const requestsBody = convertFormDataToRequestsBody(data);
console.log('__>__', data, config);
const result = await Promise.all(requestsBody.map(async(body) => {
await delay(1000); return apiFetch<'public_tag_application', unknown, { message: string }>('public_tag_application', {
onSubmitResult([ pathParams: { chainId: appConfig.chain.id },
{ error: null, payload: data }, fetchParams: {
]); method: 'POST',
}, [ config, onSubmitResult ]); body: { submission: body },
},
})
.then(() => ({ error: null, payload: body }))
.catch((error: unknown) => {
const errorObj = getErrorObj(error);
const messageFromPayload = getErrorObjPayload<{ message?: string }>(errorObj)?.message;
const messageFromError = errorObj && 'message' in errorObj && typeof errorObj.message === 'string' ? errorObj.message : undefined;
const message = messageFromPayload || messageFromError || 'Something went wrong.';
return { error: message, payload: body };
});
}));
onSubmitResult(result);
}, [ apiFetch, onSubmitResult ]);
return ( return (
<FormProvider { ...formApi }> <FormProvider { ...formApi }>
......
...@@ -10,12 +10,16 @@ const MAX_LENGTH = 80; ...@@ -10,12 +10,16 @@ const MAX_LENGTH = 80;
const PublicTagsSubmitFieldDescription = () => { const PublicTagsSubmitFieldDescription = () => {
const { control } = useFormContext<FormFields>(); const { control } = useFormContext<FormFields>();
const { field, fieldState, formState } = useController<FormFields, 'description'>({ control, name: 'description', rules: { maxLength: MAX_LENGTH } }); const { field, fieldState, formState } = useController<FormFields, 'description'>({
control,
name: 'description',
rules: { maxLength: MAX_LENGTH, required: true },
});
const isDisabled = formState.isSubmitting; const isDisabled = formState.isSubmitting;
return ( return (
<FormControl variant="floating" isDisabled={ isDisabled } size={{ base: 'md', lg: 'lg' }}> <FormControl variant="floating" isDisabled={ isDisabled } isRequired size={{ base: 'md', lg: 'lg' }}>
<Textarea <Textarea
{ ...field } { ...field }
isInvalid={ Boolean(fieldState.error) } isInvalid={ Boolean(fieldState.error) }
......
import type { FormSubmitResultItem } from './types'; import type { FormSubmitResultItem } from './types';
const address1 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5851'; export const address1 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5851';
const address2 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5852'; export const address2 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5852';
const address3 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5853'; export const address3 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5853';
const address4 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5854'; export const address4 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5854';
const address5 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5855'; export const address5 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5855';
const responseBaseFields = { export const baseFields = {
requesterName: 'John Doe', requesterName: 'John Doe',
requesterEmail: 'jonh.doe@duck.me', requesterEmail: 'jonh.doe@duck.me',
companyName: 'DuckDuckMe', companyName: 'DuckDuckMe',
...@@ -14,7 +14,7 @@ const responseBaseFields = { ...@@ -14,7 +14,7 @@ const responseBaseFields = {
description: 'Quack quack', description: 'Quack quack',
}; };
const tag1 = { export const tag1 = {
name: 'Unicorn Uproar', name: 'Unicorn Uproar',
tagType: 'name' as const, tagType: 'name' as const,
meta: { meta: {
...@@ -25,7 +25,7 @@ const tag1 = { ...@@ -25,7 +25,7 @@ const tag1 = {
}, },
}; };
const tag2 = { export const tag2 = {
name: 'Hello', name: 'Hello',
tagType: 'generic' as const, tagType: 'generic' as const,
meta: { meta: {
...@@ -33,7 +33,7 @@ const tag2 = { ...@@ -33,7 +33,7 @@ const tag2 = {
}, },
}; };
const tag3 = { export const tag3 = {
name: 'duck owner 🦆', name: 'duck owner 🦆',
tagType: 'classifier' as const, tagType: 'classifier' as const,
meta: { meta: {
...@@ -51,7 +51,7 @@ export const allSuccessResponses: Array<FormSubmitResultItem> = [ ...@@ -51,7 +51,7 @@ export const allSuccessResponses: Array<FormSubmitResultItem> = [
.map((address) => ([ tag1, tag2, tag3 ].map((tag) => ({ .map((address) => ([ tag1, tag2, tag3 ].map((tag) => ({
error: null, error: null,
payload: { payload: {
...responseBaseFields, ...baseFields,
...tag, ...tag,
address, address,
}, },
...@@ -94,4 +94,4 @@ export const mixedResponses: Array<FormSubmitResultItem> = [ ...@@ -94,4 +94,4 @@ export const mixedResponses: Array<FormSubmitResultItem> = [
error: null, error: null,
payload: { address: address3, ...tag3 }, payload: { address: address3, ...tag3 },
}, },
].map((item) => ({ ...item, payload: { ...item.payload, ...responseBaseFields } })); ].map((item) => ({ ...item, payload: { ...item.payload, ...baseFields } }));
import * as mocks from './mocks';
import { convertFormDataToRequestsBody, convertTagApiFieldsToFormFields, groupSubmitResult } from './utils';
describe('function convertFormDataToRequestsBody()', () => {
it('should convert form data to requests body', () => {
const formData = {
...mocks.baseFields,
reCaptcha: 'xxx',
addresses: [ { hash: mocks.address1 }, { hash: mocks.address2 } ],
tags: [ convertTagApiFieldsToFormFields(mocks.tag1), convertTagApiFieldsToFormFields(mocks.tag2) ],
};
const result = convertFormDataToRequestsBody(formData);
expect(result).toMatchObject([
{ address: mocks.address1, name: mocks.tag1.name, tagType: mocks.tag1.tagType },
{ address: mocks.address1, name: mocks.tag2.name, tagType: mocks.tag2.tagType },
{ address: mocks.address2, name: mocks.tag1.name, tagType: mocks.tag1.tagType },
{ address: mocks.address2, name: mocks.tag2.name, tagType: mocks.tag2.tagType },
]);
});
});
describe('function groupSubmitResult()', () => {
it('group success result', () => {
const result = groupSubmitResult(mocks.allSuccessResponses);
expect(result).toMatchObject({
requesterName: mocks.baseFields.requesterName,
requesterEmail: mocks.baseFields.requesterEmail,
companyName: mocks.baseFields.companyName,
companyWebsite: mocks.baseFields.companyWebsite,
items: [
{
error: null,
addresses: [ mocks.address1, mocks.address2, mocks.address3, mocks.address4, mocks.address5 ],
tags: [ mocks.tag1, mocks.tag2, mocks.tag3 ],
},
],
});
});
it('group result with error', () => {
const result = groupSubmitResult(mocks.mixedResponses);
expect(result).toMatchObject({
requesterName: mocks.baseFields.requesterName,
requesterEmail: mocks.baseFields.requesterEmail,
companyName: mocks.baseFields.companyName,
companyWebsite: mocks.baseFields.companyWebsite,
items: [
{
error: null,
addresses: [ mocks.address1 ],
tags: [ mocks.tag1 ],
},
{
error: null,
addresses: [ mocks.address3 ],
tags: [ mocks.tag3 ],
},
{
error: 'Some error',
addresses: [ mocks.address1, mocks.address2 ],
tags: [ mocks.tag2, mocks.tag3 ],
},
{
error: 'Some error',
addresses: [ mocks.address3 ],
tags: [ mocks.tag1 ],
},
{
error: 'Another nasty error',
addresses: [ mocks.address3 ],
tags: [ mocks.tag2 ],
},
],
});
});
});
import _isEqual from 'lodash/isEqual'; import _isEqual from 'lodash/isEqual';
import _pickBy from 'lodash/pickBy';
import type { FormSubmitResult, FormSubmitResultGrouped, FormSubmitResultItemGrouped } from './types'; import type { FormFieldTag, FormFields, FormSubmitResult, FormSubmitResultGrouped, FormSubmitResultItemGrouped, SubmitRequestBody } from './types';
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
export function convertFormDataToRequestsBody(data: FormFields): Array<SubmitRequestBody> {
const result: Array<SubmitRequestBody> = [];
for (const address of data.addresses) {
for (const tag of data.tags) {
result.push({
requesterName: data.requesterName,
requesterEmail: data.requesterEmail,
companyName: data.companyName,
companyWebsite: data.companyWebsite,
address: address.hash,
name: tag.name,
tagType: tag.type.value,
description: data.description,
meta: _pickBy({
bgColor: tag.bgColor,
textColor: tag.textColor,
tagUrl: tag.url,
tooltipDescription: tag.tooltipDescription,
}, Boolean),
});
}
}
return result;
}
export function convertTagApiFieldsToFormFields(tag: Pick<SubmitRequestBody, 'name' | 'tagType' | 'meta'>): FormFieldTag {
return {
name: tag.name,
type: { label: tag.tagType, value: tag.tagType },
url: tag.meta.tagUrl,
bgColor: tag.meta.bgColor,
textColor: tag.meta.textColor,
tooltipDescription: tag.meta.tooltipDescription,
};
}
export function groupSubmitResult(data: FormSubmitResult | undefined): FormSubmitResultGrouped | undefined { export function groupSubmitResult(data: FormSubmitResult | undefined): FormSubmitResultGrouped | undefined {
if (!data) { if (!data) {
return; return;
......
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