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 = {
filterFields: [ 'name' as const, 'only_active' as const ],
},
// METADATA SERVICE
// METADATA SERVICE & PUBLIC TAGS
address_metadata_info: {
path: '/api/v1/metadata',
endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint,
......@@ -256,6 +256,12 @@ export const RESOURCES = {
endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint,
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
visualize_sol2uml: {
......
......@@ -7,7 +7,10 @@ import { useForm, FormProvider } from 'react-hook-form';
import type { FormFields, FormSubmitResult } from './types';
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 FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha';
import Hint from 'ui/shared/Hint';
......@@ -20,7 +23,7 @@ import PublicTagsSubmitFieldRequesterEmail from './fields/PublicTagsSubmitFieldR
import PublicTagsSubmitFieldRequesterName from './fields/PublicTagsSubmitFieldRequesterName';
import PublicTagsSubmitFieldTags from './fields/PublicTagsSubmitFieldTags';
import * as mocks from './mocks';
import { getDefaultValuesFromQuery } from './utils';
import { convertFormDataToRequestsBody, getDefaultValuesFromQuery } from './utils';
interface Props {
config: PublicTagTypesResponse | undefined;
......@@ -30,6 +33,7 @@ interface Props {
const PublicTagsSubmitForm = ({ config, onSubmitResult }: Props) => {
const isMobile = useIsMobile();
const router = useRouter();
const apiFetch = useApiFetch();
const formApi = useForm<FormFields>({
mode: 'onBlur',
......@@ -53,14 +57,28 @@ const PublicTagsSubmitForm = ({ config, onSubmitResult }: Props) => {
}, [ onSubmitResult ]);
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
// eslint-disable-next-line no-console
console.log('__>__', data, config);
await delay(1000);
onSubmitResult([
{ error: null, payload: data },
]);
}, [ config, onSubmitResult ]);
const requestsBody = convertFormDataToRequestsBody(data);
const result = await Promise.all(requestsBody.map(async(body) => {
return apiFetch<'public_tag_application', unknown, { message: string }>('public_tag_application', {
pathParams: { chainId: appConfig.chain.id },
fetchParams: {
method: 'POST',
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 (
<FormProvider { ...formApi }>
......
......@@ -10,12 +10,16 @@ const MAX_LENGTH = 80;
const PublicTagsSubmitFieldDescription = () => {
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;
return (
<FormControl variant="floating" isDisabled={ isDisabled } size={{ base: 'md', lg: 'lg' }}>
<FormControl variant="floating" isDisabled={ isDisabled } isRequired size={{ base: 'md', lg: 'lg' }}>
<Textarea
{ ...field }
isInvalid={ Boolean(fieldState.error) }
......
import type { FormSubmitResultItem } from './types';
const address1 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5851';
const address2 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5852';
const address3 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5853';
const address4 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5854';
const address5 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5855';
export const address1 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5851';
export const address2 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5852';
export const address3 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5853';
export const address4 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5854';
export const address5 = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5855';
const responseBaseFields = {
export const baseFields = {
requesterName: 'John Doe',
requesterEmail: 'jonh.doe@duck.me',
companyName: 'DuckDuckMe',
......@@ -14,7 +14,7 @@ const responseBaseFields = {
description: 'Quack quack',
};
const tag1 = {
export const tag1 = {
name: 'Unicorn Uproar',
tagType: 'name' as const,
meta: {
......@@ -25,7 +25,7 @@ const tag1 = {
},
};
const tag2 = {
export const tag2 = {
name: 'Hello',
tagType: 'generic' as const,
meta: {
......@@ -33,7 +33,7 @@ const tag2 = {
},
};
const tag3 = {
export const tag3 = {
name: 'duck owner 🦆',
tagType: 'classifier' as const,
meta: {
......@@ -51,7 +51,7 @@ export const allSuccessResponses: Array<FormSubmitResultItem> = [
.map((address) => ([ tag1, tag2, tag3 ].map((tag) => ({
error: null,
payload: {
...responseBaseFields,
...baseFields,
...tag,
address,
},
......@@ -94,4 +94,4 @@ export const mixedResponses: Array<FormSubmitResultItem> = [
error: null,
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 _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 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 {
if (!data) {
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