Commit b8789b81 authored by tom's avatar tom

token info form

parent 62d5b8e4
...@@ -20,12 +20,12 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>( ...@@ -20,12 +20,12 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
const { label, children, helperText, errorText, optionalText, ...rest } = props; const { label, children, helperText, errorText, optionalText, ...rest } = props;
// A floating field cannot be without a label. // A floating field cannot be without a label.
if (props.floating && label) { if (rest.floating && label) {
const injectedProps = { const injectedProps = {
className: 'peer', className: 'peer',
placeholder: ' ', placeholder: ' ',
size: props.size, size: rest.size,
floating: props.floating, floating: rest.floating,
bgColor: rest.bgColor, bgColor: rest.bgColor,
disabled: rest.disabled, disabled: rest.disabled,
readOnly: rest.readOnly, readOnly: rest.readOnly,
...@@ -79,6 +79,13 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>( ...@@ -79,6 +79,13 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
); );
} }
// Pass size value to the input component
const injectedProps = {
size: rest.size,
};
const child = React.Children.only<React.ReactElement<InputProps | InputGroupProps>>(children);
const clonedChild = React.cloneElement(child, injectedProps);
return ( return (
<ChakraField.Root ref={ ref } { ...rest }> <ChakraField.Root ref={ ref } { ...rest }>
{ label && ( { label && (
...@@ -87,7 +94,7 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>( ...@@ -87,7 +94,7 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
<ChakraField.RequiredIndicator fallback={ optionalText }/> <ChakraField.RequiredIndicator fallback={ optionalText }/>
</ChakraField.Label> </ChakraField.Label>
) } ) }
{ children } { clonedChild }
{ helperText && ( { helperText && (
<ChakraField.HelperText>{ helperText }</ChakraField.HelperText> <ChakraField.HelperText>{ helperText }</ChakraField.HelperText>
) } ) }
......
...@@ -117,6 +117,9 @@ export const recipe = defineSlotRecipe({ ...@@ -117,6 +117,9 @@ export const recipe = defineSlotRecipe({
floating: true, floating: true,
css: { css: {
label: { label: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
padding: '10px 16px 0px 16px', padding: '10px 16px 0px 16px',
textStyle: 'xs', textStyle: 'xs',
_peerPlaceholderShown: { _peerPlaceholderShown: {
...@@ -142,6 +145,9 @@ export const recipe = defineSlotRecipe({ ...@@ -142,6 +145,9 @@ export const recipe = defineSlotRecipe({
floating: true, floating: true,
css: { css: {
label: { label: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
// 16px = scrollbar width // 16px = scrollbar width
width: 'calc(100% - 4px - 20px)', width: 'calc(100% - 4px - 20px)',
padding: '20px 24px 0px 24px', padding: '20px 24px 0px 24px',
......
...@@ -12,7 +12,6 @@ import getQueryParamString from 'lib/router/getQueryParamString'; ...@@ -12,7 +12,6 @@ import getQueryParamString from 'lib/router/getQueryParamString';
import { TOKEN_INFO_APPLICATION, VERIFIED_ADDRESS } from 'stubs/account'; import { TOKEN_INFO_APPLICATION, VERIFIED_ADDRESS } from 'stubs/account';
import { Button } from 'toolkit/chakra/button'; import { Button } from 'toolkit/chakra/button';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import AddressVerificationModal from 'ui/addressVerification/AddressVerificationModal'; import AddressVerificationModal from 'ui/addressVerification/AddressVerificationModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
...@@ -144,12 +143,12 @@ const VerifiedAddresses = () => { ...@@ -144,12 +143,12 @@ const VerifiedAddresses = () => {
return ( return (
<> <>
<PageTitle title="Token info application form" backLink={ backLink }/> <PageTitle title="Token info application form" backLink={ backLink }/>
{ /* <TokenInfoForm <TokenInfoForm
address={ selectedAddress } address={ selectedAddress }
tokenName={ tokenName } tokenName={ tokenName }
application={ applicationsQuery.data?.submissions.find(({ tokenAddress }) => tokenAddress.toLowerCase() === selectedAddress.toLowerCase()) } application={ applicationsQuery.data?.submissions.find(({ tokenAddress }) => tokenAddress.toLowerCase() === selectedAddress.toLowerCase()) }
onSubmit={ handleApplicationSubmit } onSubmit={ handleApplicationSubmit }
/> */ } />
</> </>
); );
} }
......
import type { ColorMode } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import { Image, chakra, DarkMode } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton'; import type { ColorMode } from 'toolkit/chakra/color-mode';
import { Image } from 'toolkit/chakra/image';
import { Skeleton } from 'toolkit/chakra/skeleton';
interface Props { interface Props {
src: string | undefined; src: string | undefined;
...@@ -23,11 +24,11 @@ const ImageUrlPreview = ({ ...@@ -23,11 +24,11 @@ const ImageUrlPreview = ({
fallback: fallbackProp, fallback: fallbackProp,
colorMode, colorMode,
}: Props) => { }: Props) => {
const skeleton = <Skeleton className={ className } w="100%" h="100%"/>; const skeleton = <Skeleton loading className={ [ className, colorMode === 'dark' ? 'dark' : undefined ].filter(Boolean).join(' ') } w="100%" h="100%"/>;
const fallback = (() => { const fallback = (() => {
if (src && !isInvalid) { if (src && !isInvalid) {
return colorMode === 'dark' ? <DarkMode>{ skeleton }</DarkMode> : skeleton; return skeleton;
} }
return fallbackProp; return fallbackProp;
})(); })();
......
...@@ -13,6 +13,7 @@ import FancySelect from 'ui/shared/forms/inputs/select/FancySelect'; ...@@ -13,6 +13,7 @@ import FancySelect from 'ui/shared/forms/inputs/select/FancySelect';
// this type only works for plain objects, not for nested objects or arrays (e.g. ui/publicTags/submit/types.ts:FormFields) // this type only works for plain objects, not for nested objects or arrays (e.g. ui/publicTags/submit/types.ts:FormFields)
// type SelectField<O> = { [K in keyof O]: NonNullable<O[K]> extends Option ? K : never }[keyof O]; // type SelectField<O> = { [K in keyof O]: NonNullable<O[K]> extends Option ? K : never }[keyof O];
// TODO @tom2drum remove this component
type Props< type Props<
FormFields extends FieldValues, FormFields extends FieldValues,
Name extends Path<FormFields>, Name extends Path<FormFields>,
......
import React from 'react';
import type { Path, FieldValues } from 'react-hook-form';
import { useController, useFormContext } from 'react-hook-form';
import type { FormFieldPropsBase } from './types';
import type { SelectRootProps } from 'toolkit/chakra/select';
import { SelectContent, SelectControl, SelectItem, SelectRoot, SelectValueText } from 'toolkit/chakra/select';
type Props<
FormFields extends FieldValues,
Name extends Path<FormFields>,
> = FormFieldPropsBase<FormFields, Name> & SelectRootProps;
const FormFieldSelect = <
FormFields extends FieldValues,
Name extends Path<FormFields>,
>(props: Props<FormFields, Name>) => {
const { name, rules, collection, placeholder, ...rest } = props;
const { control } = useFormContext<FormFields>();
const { field, fieldState, formState } = useController<FormFields, typeof name>({
control,
name,
rules,
});
const isDisabled = formState.isSubmitting;
const handleChange = React.useCallback(({ value }: { value: Array<string> }) => {
field.onChange(value);
}, [ field ]);
const handleBlur = React.useCallback(() => {
field.onBlur();
}, [ field ]);
// TODO @tom2drum: fix initial value is not displayed in the select
return (
<SelectRoot
ref={ field.ref }
name={ field.name }
value={ field.value }
onValueChange={ handleChange }
onInteractOutside={ handleBlur }
collection={ collection }
disabled={ isDisabled }
invalid={ Boolean(fieldState.error) }
{ ...rest }
>
<SelectControl>
<SelectValueText placeholder={ placeholder }/>
</SelectControl>
<SelectContent>
{ collection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
);
};
export default React.memo(FormFieldSelect) as typeof FormFieldSelect;
...@@ -15,32 +15,32 @@ const FieldShowcase = () => { ...@@ -15,32 +15,32 @@ const FieldShowcase = () => {
{ ([ 'sm', 'md', 'lg' ] as const).map((size) => ( { ([ 'sm', 'md', 'lg' ] as const).map((size) => (
<Sample label={ `size: ${ size }` } w="100%" key={ size } alignItems="flex-start"> <Sample label={ `size: ${ size }` } w="100%" key={ size } alignItems="flex-start">
<Field label="Email" required size={ size } helperText="Helper text" maxWidth="200px"> <Field label="Email" required size={ size } helperText="Helper text" maxWidth="200px">
<Input size={ size }/> <Input/>
</Field> </Field>
<Field label="Email (disabled)" required size={ size } maxWidth="200px"> <Field label="Email (disabled)" required size={ size } maxWidth="200px">
<Input size={ size } disabled value="me@example.com"/> <Input disabled value="me@example.com"/>
</Field> </Field>
<Field label="Email (readOnly)" required size={ size } maxWidth="200px"> <Field label="Email (readOnly)" required size={ size } maxWidth="200px">
<Input size={ size } readOnly value="me@example.com"/> <Input readOnly value="me@example.com"/>
</Field> </Field>
<Field label="Email (invalid)" required size={ size } errorText="Something went wrong" invalid maxWidth="200px"> <Field label="Email (invalid)" required size={ size } errorText="Something went wrong" invalid maxWidth="200px">
<Input size={ size } value="duck"/> <Input value="duck"/>
</Field> </Field>
</Sample> </Sample>
)) } )) }
<Sample label="size: xl" w="100%" alignItems="flex-start"> <Sample label="size: xl" w="100%" alignItems="flex-start">
<Field label="Email" required floating size="xl" helperText="Helper text" maxWidth="300px"> <Field label="Email" required floating size="xl" helperText="Helper text" maxWidth="300px">
<Input size="xl"/> <Input/>
</Field> </Field>
<Field label="Email (disabled)" required floating disabled size="xl" maxWidth="300px"> <Field label="Email (disabled)" required floating disabled size="xl" maxWidth="300px">
<Input size="xl" value="me@example.com"/> <Input value="me@example.com"/>
</Field> </Field>
<Field label="Email (readOnly)" required floating readOnly size="xl" maxWidth="300px"> <Field label="Email (readOnly)" required floating readOnly size="xl" maxWidth="300px">
<Input size="xl" value="me@example.com"/> <Input value="me@example.com"/>
</Field> </Field>
<Field label="Email (invalid)" required floating size="xl" errorText="Something went wrong" invalid maxWidth="300px"> <Field label="Email (invalid)" required floating size="xl" errorText="Something went wrong" invalid maxWidth="300px">
<Input size="xl" value="duck"/> <Input value="duck"/>
</Field> </Field>
</Sample> </Sample>
</SamplesStack> </SamplesStack>
...@@ -63,16 +63,16 @@ const FieldShowcase = () => { ...@@ -63,16 +63,16 @@ const FieldShowcase = () => {
</Sample> </Sample>
<Sample label="floating label" p={ 4 } bgColor={{ _light: 'blackAlpha.200', _dark: 'whiteAlpha.200' }} alignItems="flex-start"> <Sample label="floating label" p={ 4 } bgColor={{ _light: 'blackAlpha.200', _dark: 'whiteAlpha.200' }} alignItems="flex-start">
<Field label="Email" required floating size="xl" helperText="Helper text" maxWidth="300px"> <Field label="Email" required floating size="xl" helperText="Helper text" maxWidth="300px">
<Input size="xl"/> <Input/>
</Field> </Field>
<Field label="Email (disabled)" required disabled floating size="xl" maxWidth="300px"> <Field label="Email (disabled)" required disabled floating size="xl" maxWidth="300px">
<Input size="xl" value="me@example.com"/> <Input value="me@example.com"/>
</Field> </Field>
<Field label="Email (readOnly)" required readOnly floating size="xl" maxWidth="300px"> <Field label="Email (readOnly)" required readOnly floating size="xl" maxWidth="300px">
<Input size="xl" value="me@example.com"/> <Input value="me@example.com"/>
</Field> </Field>
<Field label="Email (invalid)" required floating size="xl" errorText="Something went wrong" invalid maxWidth="300px"> <Field label="Email (invalid)" required floating size="xl" errorText="Something went wrong" invalid maxWidth="300px">
<Input size="xl" value="duck"/> <Input value="duck"/>
</Field> </Field>
</Sample> </Sample>
</SamplesStack> </SamplesStack>
......
import { Button, Grid, GridItem, Text } from '@chakra-ui/react'; import { Grid, GridItem, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
...@@ -10,9 +10,10 @@ import config from 'configs/app'; ...@@ -10,9 +10,10 @@ import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import useToast from 'lib/hooks/useToast';
import useUpdateEffect from 'lib/hooks/useUpdateEffect'; import useUpdateEffect from 'lib/hooks/useUpdateEffect';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { Button } from 'toolkit/chakra/button';
import { toaster } from 'toolkit/chakra/toaster';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress';
...@@ -42,7 +43,6 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) => ...@@ -42,7 +43,6 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
const openEventSent = React.useRef<boolean>(false); const openEventSent = React.useRef<boolean>(false);
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const toast = useToast();
const configQuery = useApiQuery('token_info_applications_config', { const configQuery = useApiQuery('token_info_applications_config', {
pathParams: { chainId: config.chain.id }, pathParams: { chainId: config.chain.id },
...@@ -85,16 +85,12 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) => ...@@ -85,16 +85,12 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
throw result; throw result;
} }
} catch (error) { } catch (error) {
toast({ toaster.error({
position: 'top-right',
title: 'Error', title: 'Error',
description: (error as ResourceError<{ message: string }>)?.payload?.message || 'Something went wrong. Try again later.', description: (error as ResourceError<{ message: string }>)?.payload?.message || 'Something went wrong. Try again later.',
status: 'error',
variant: 'subtle',
isClosable: true,
}); });
} }
}, [ apiFetch, application?.id, application?.status, onSubmit, toast ]); }, [ apiFetch, application?.id, application?.status, onSubmit ]);
useUpdateEffect(() => { useUpdateEffect(() => {
if (formState.submitCount > 0 && !formState.isValid) { if (formState.submitCount > 0 && !formState.isValid) {
...@@ -119,8 +115,8 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) => ...@@ -119,8 +115,8 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
} }
const fieldProps = { const fieldProps = {
size: { base: 'md', lg: 'lg' }, size: 'xl' as const,
isReadOnly: application?.status === 'IN_PROCESS', readOnly: application?.status === 'IN_PROCESS',
}; };
return ( return (
...@@ -129,16 +125,16 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) => ...@@ -129,16 +125,16 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
<TokenInfoFormStatusText application={ application }/> <TokenInfoFormStatusText application={ application }/>
<Grid mt={ 8 } gridTemplateColumns={{ base: '1fr', lg: '1fr 1fr' }} columnGap={ 5 } rowGap={ 5 }> <Grid mt={ 8 } gridTemplateColumns={{ base: '1fr', lg: '1fr 1fr' }} columnGap={ 5 } rowGap={ 5 }>
<FormFieldText<Fields> name="token_name" isRequired placeholder="Token name" { ...fieldProps } isReadOnly/> <FormFieldText<Fields> name="token_name" required placeholder="Token name" { ...fieldProps } readOnly/>
<FormFieldAddress<Fields> name="address" isRequired placeholder="Token contract address" { ...fieldProps } isReadOnly/> <FormFieldAddress<Fields> name="address" required placeholder="Token contract address" { ...fieldProps } readOnly/>
<FormFieldText<Fields> name="requester_name" isRequired placeholder="Requester name" { ...fieldProps }/> <FormFieldText<Fields> name="requester_name" required placeholder="Requester name" { ...fieldProps }/>
<FormFieldEmail<Fields> name="requester_email" isRequired placeholder="Requester email" { ...fieldProps }/> <FormFieldEmail<Fields> name="requester_email" required placeholder="Requester email" { ...fieldProps }/>
<TokenInfoFormSectionHeader>Project info</TokenInfoFormSectionHeader> <TokenInfoFormSectionHeader>Project info</TokenInfoFormSectionHeader>
<FormFieldText<Fields> name="project_name" placeholder="Project name" { ...fieldProps } rules={ nonWhitespaceFieldRules }/> <FormFieldText<Fields> name="project_name" placeholder="Project name" { ...fieldProps } rules={ nonWhitespaceFieldRules }/>
<TokenInfoFieldProjectSector { ...fieldProps } config={ configQuery.data.projectSectors }/> <TokenInfoFieldProjectSector { ...fieldProps } config={ configQuery.data.projectSectors }/>
<FormFieldEmail<Fields> name="project_email" isRequired placeholder="Official project email address" { ...fieldProps }/> <FormFieldEmail<Fields> name="project_email" required placeholder="Official project email address" { ...fieldProps }/>
<FormFieldUrl<Fields> name="project_website" isRequired placeholder="Official project website" { ...fieldProps }/> <FormFieldUrl<Fields> name="project_website" required placeholder="Official project website" { ...fieldProps }/>
<FormFieldUrl<Fields> name="docs" placeholder="Docs" { ...fieldProps }/> <FormFieldUrl<Fields> name="docs" placeholder="Docs" { ...fieldProps }/>
<TokenInfoFieldSupport { ...fieldProps }/> <TokenInfoFieldSupport { ...fieldProps }/>
<GridItem colSpan={{ base: 1, lg: 2 }}> <GridItem colSpan={{ base: 1, lg: 2 }}>
...@@ -147,14 +143,14 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) => ...@@ -147,14 +143,14 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
<GridItem colSpan={{ base: 1, lg: 2 }}> <GridItem colSpan={{ base: 1, lg: 2 }}>
<FormFieldText<Fields> <FormFieldText<Fields>
name="project_description" name="project_description"
isRequired required
placeholder="Project description" placeholder="Project description"
maxH="160px" maxH="160px"
rules={{ maxLength: 300, ...nonWhitespaceFieldRules }} rules={{ maxLength: 300, ...nonWhitespaceFieldRules }}
asComponent="Textarea" asComponent="Textarea"
{ ...fieldProps } { ...fieldProps }
/> />
<Text variant="secondary" fontSize="sm" mt={ 1 }> <Text color="text.secondary" fontSize="sm" mt={ 1 }>
Introduce or summarize the project’s operation/goals in a maximum of 300 characters. Introduce or summarize the project’s operation/goals in a maximum of 300 characters.
The description should be written in a neutral point of view and must exclude unsubstantiated claims unless proven otherwise. The description should be written in a neutral point of view and must exclude unsubstantiated claims unless proven otherwise.
</Text> </Text>
...@@ -194,9 +190,9 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) => ...@@ -194,9 +190,9 @@ const TokenInfoForm = ({ address, tokenName, application, onSubmit }: Props) =>
type="submit" type="submit"
size="lg" size="lg"
mt={ 8 } mt={ 8 }
isLoading={ formState.isSubmitting } loading={ formState.isSubmitting }
loadingText="Send request" loadingText="Send request"
isDisabled={ application?.status === 'IN_PROCESS' } disabled={ application?.status === 'IN_PROCESS' }
> >
Send request Send request
</Button> </Button>
......
import { GridItem } from '@chakra-ui/react'; import { GridItem } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Heading } from 'toolkit/chakra/heading';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
} }
const TokenInfoFormSectionHeader = ({ children }: Props) => { const TokenInfoFormSectionHeader = ({ children }: Props) => {
return ( return (
<GridItem colSpan={{ base: 1, lg: 2 }} fontFamily="heading" fontSize="lg" fontWeight={ 500 } mt={ 3 }> <GridItem colSpan={{ base: 1, lg: 2 }} mt={ 3 }>
<Heading level="2">
{ children } { children }
</Heading>
</GridItem> </GridItem>
); );
}; };
......
import { Alert } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfoApplication } from 'types/api/account'; import type { TokenInfoApplication } from 'types/api/account';
import { Alert } from 'toolkit/chakra/alert';
interface Props { interface Props {
application?: TokenInfoApplication; application?: TokenInfoApplication;
} }
......
import { Center, useColorModeValue } from '@chakra-ui/react'; import { Center } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
...@@ -8,13 +8,13 @@ interface Props { ...@@ -8,13 +8,13 @@ interface Props {
} }
const TokenInfoIconPreview = ({ url, isInvalid, children }: Props) => { const TokenInfoIconPreview = ({ url, isInvalid, children }: Props) => {
const borderColor = useColorModeValue('gray.100', 'gray.700'); const borderColor = { _light: 'gray.100', _dark: 'gray.700' };
const borderColorFilled = useColorModeValue('gray.300', 'gray.600'); const borderColorFilled = { _light: 'gray.300', _dark: 'gray.600' };
const borderColorActive = isInvalid ? 'error' : borderColorFilled; const borderColorActive = isInvalid ? 'error' : borderColorFilled;
return ( return (
<Center <Center
boxSize={{ base: '60px', lg: '80px' }} boxSize="60px"
flexShrink={ 0 } flexShrink={ 0 }
borderWidth="2px" borderWidth="2px"
borderColor={ url ? borderColorActive : borderColor } borderColor={ url ? borderColorActive : borderColor }
......
import type { FormControlProps } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Fields } from '../types'; import type { Fields } from '../types';
import { times } from 'lib/html-entities'; import { times } from 'lib/html-entities';
import type { FieldProps } from 'toolkit/chakra/field';
import ImageUrlPreview from 'ui/shared/forms/components/ImageUrlPreview'; import ImageUrlPreview from 'ui/shared/forms/components/ImageUrlPreview';
import FormFieldUrl from 'ui/shared/forms/fields/FormFieldUrl'; import FormFieldUrl from 'ui/shared/forms/fields/FormFieldUrl';
import useFieldWithImagePreview from 'ui/shared/forms/utils/useFieldWithImagePreview'; import useFieldWithImagePreview from 'ui/shared/forms/utils/useFieldWithImagePreview';
...@@ -13,11 +13,11 @@ import TokenLogoPlaceholder from 'ui/shared/TokenLogoPlaceholder'; ...@@ -13,11 +13,11 @@ import TokenLogoPlaceholder from 'ui/shared/TokenLogoPlaceholder';
import TokenInfoIconPreview from '../TokenInfoIconPreview'; import TokenInfoIconPreview from '../TokenInfoIconPreview';
interface Props { interface Props {
isReadOnly?: boolean; readOnly?: boolean;
size?: FormControlProps['size']; size?: FieldProps['size'];
} }
const TokenInfoFieldIconUrl = ({ isReadOnly, size }: Props) => { const TokenInfoFieldIconUrl = ({ readOnly, size }: Props) => {
const previewUtils = useFieldWithImagePreview({ name: 'icon_url', isRequired: true }); const previewUtils = useFieldWithImagePreview({ name: 'icon_url', isRequired: true });
...@@ -26,15 +26,15 @@ const TokenInfoFieldIconUrl = ({ isReadOnly, size }: Props) => { ...@@ -26,15 +26,15 @@ const TokenInfoFieldIconUrl = ({ isReadOnly, size }: Props) => {
<FormFieldUrl<Fields> <FormFieldUrl<Fields>
name="icon_url" name="icon_url"
placeholder={ `Link to icon URL, link to download a SVG or 48${ times }48 PNG icon logo` } placeholder={ `Link to icon URL, link to download a SVG or 48${ times }48 PNG icon logo` }
isReadOnly={ isReadOnly } readOnly={ readOnly }
size={ size } size={ size }
{ ...previewUtils.input } { ...previewUtils.input }
/> />
<TokenInfoIconPreview url={ previewUtils.preview.src } isInvalid={ previewUtils.preview.isInvalid }> <TokenInfoIconPreview url={ previewUtils.preview.src } isInvalid={ previewUtils.preview.isInvalid }>
<ImageUrlPreview <ImageUrlPreview
{ ...previewUtils.preview } { ...previewUtils.preview }
fallback={ <TokenLogoPlaceholder boxSize={{ base: 10, lg: 12 }}/> } fallback={ <TokenLogoPlaceholder boxSize={ 10 }/> }
boxSize={{ base: 10, lg: 12 }} boxSize={ 10 }
borderRadius="base" borderRadius="base"
/> />
</TokenInfoIconPreview> </TokenInfoIconPreview>
......
import { createListCollection } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Fields } from '../types'; import type { Fields } from '../types';
import type { TokenInfoApplicationConfig } from 'types/api/account'; import type { TokenInfoApplicationConfig } from 'types/api/account';
import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect'; import FormFieldSelect from 'ui/shared/forms/fields/FormFieldSelect';
interface Props { interface Props {
isReadOnly?: boolean; readOnly?: boolean;
config: TokenInfoApplicationConfig['projectSectors']; config: TokenInfoApplicationConfig['projectSectors'];
} }
const TokenInfoFieldProjectSector = ({ isReadOnly, config }: Props) => { const TokenInfoFieldProjectSector = ({ readOnly, config }: Props) => {
const options = React.useMemo(() => { const collection = React.useMemo(() => {
return config.map((option) => ({ label: option, value: option })); const items = config.map((option) => ({ label: option, value: option }));
return createListCollection({ items });
}, [ config ]); }, [ config ]);
return ( return (
<FormFieldFancySelect<Fields, 'project_sector'> <FormFieldSelect<Fields, 'project_sector'>
name="project_sector" name="project_sector"
placeholder="Project industry" placeholder="Project industry"
options={ options } collection={ collection }
isReadOnly={ isReadOnly } readOnly={ readOnly }
/> />
); );
}; };
......
import type { FormControlProps } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form'; import type { ControllerRenderProps } from 'react-hook-form';
import type { Fields, SocialLinkFields } from '../types'; import type { Fields, SocialLinkFields } from '../types';
import type { FieldProps } from 'toolkit/chakra/field';
import FormFieldUrl from 'ui/shared/forms/fields/FormFieldUrl'; import FormFieldUrl from 'ui/shared/forms/fields/FormFieldUrl';
import type { IconName } from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -14,36 +14,38 @@ interface Item { ...@@ -14,36 +14,38 @@ interface Item {
color: string; color: string;
} }
const SETTINGS: Record<keyof SocialLinkFields, Item> = { const SETTINGS: Record<keyof SocialLinkFields, Item> = {
github: { label: 'GitHub', icon: 'social/github_filled', color: 'inherit' }, github: { label: 'GitHub', icon: 'social/github_filled', color: 'text.primary' },
telegram: { label: 'Telegram', icon: 'social/telegram_filled', color: 'telegram' }, telegram: { label: 'Telegram', icon: 'social/telegram_filled', color: 'telegram' },
linkedin: { label: 'LinkedIn', icon: 'social/linkedin_filled', color: 'linkedin' }, linkedin: { label: 'LinkedIn', icon: 'social/linkedin_filled', color: 'linkedin' },
discord: { label: 'Discord', icon: 'social/discord_filled', color: 'discord' }, discord: { label: 'Discord', icon: 'social/discord_filled', color: 'discord' },
slack: { label: 'Slack', icon: 'social/slack_filled', color: 'slack' }, slack: { label: 'Slack', icon: 'social/slack_filled', color: 'slack' },
twitter: { label: 'X (ex-Twitter)', icon: 'social/twitter_filled', color: 'inherit' }, twitter: { label: 'X (ex-Twitter)', icon: 'social/twitter_filled', color: 'text.primary' },
opensea: { label: 'OpenSea', icon: 'social/opensea_filled', color: 'opensea' }, opensea: { label: 'OpenSea', icon: 'social/opensea_filled', color: 'opensea' },
facebook: { label: 'Facebook', icon: 'social/facebook_filled', color: 'facebook' }, facebook: { label: 'Facebook', icon: 'social/facebook_filled', color: 'facebook' },
medium: { label: 'Medium', icon: 'social/medium_filled', color: 'inherit' }, medium: { label: 'Medium', icon: 'social/medium_filled', color: 'text.primary' },
reddit: { label: 'Reddit', icon: 'social/reddit_filled', color: 'reddit' }, reddit: { label: 'Reddit', icon: 'social/reddit_filled', color: 'reddit' },
}; };
interface Props { interface Props {
isReadOnly?: boolean; readOnly?: boolean;
size?: FormControlProps['size']; size?: FieldProps['size'];
name: keyof SocialLinkFields; name: keyof SocialLinkFields;
} }
const TokenInfoFieldSocialLink = ({ isReadOnly, size, name }: Props) => { const TokenInfoFieldSocialLink = ({ readOnly, size, name }: Props) => {
const rightElement = React.useCallback(({ field }: { field: ControllerRenderProps<Fields, keyof SocialLinkFields> }) => { const endElement = React.useCallback(({ field }: { field: ControllerRenderProps<Fields> }) => {
return <IconSvg name={ SETTINGS[name].icon } boxSize={ 6 } color={ field.value ? SETTINGS[name].color : '#718096' }/>; return <IconSvg name={ SETTINGS[name].icon } boxSize="60px" px={ 4 } color={ field.value ? SETTINGS[name].color : '#718096' }/>;
}, [ name ]); }, [ name ]);
return ( return (
<FormFieldUrl<Fields, keyof SocialLinkFields> <FormFieldUrl<Fields>
name={ name } name={ name }
placeholder={ SETTINGS[name].label } placeholder={ SETTINGS[name].label }
rightElement={ rightElement } group={{
isReadOnly={ isReadOnly } endElement,
}}
readOnly={ readOnly }
size={ size } size={ size }
/> />
); );
......
import type { InputProps } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Fields } from '../types'; import type { Fields } from '../types';
import type { FieldProps } from 'toolkit/chakra/field';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
import { validator as emailValidator } from 'ui/shared/forms/validators/email'; import { validator as emailValidator } from 'ui/shared/forms/validators/email';
import { urlValidator } from 'ui/shared/forms/validators/url'; import { urlValidator } from 'ui/shared/forms/validators/url';
interface Props { interface Props {
isReadOnly?: boolean; readOnly?: boolean;
size?: InputProps['size']; size?: FieldProps['size'];
} }
const TokenInfoFieldSupport = (props: Props) => { const TokenInfoFieldSupport = (props: Props) => {
......
...@@ -6,7 +6,7 @@ export interface Fields extends SocialLinkFields, TickerUrlFields { ...@@ -6,7 +6,7 @@ export interface Fields extends SocialLinkFields, TickerUrlFields {
requester_name: string; requester_name: string;
requester_email: string; requester_email: string;
project_name?: string; project_name?: string;
project_sector: Option | null; project_sector: Array<Option> | null;
project_email: string; project_email: string;
project_website: string; project_website: string;
project_description: string; project_description: string;
......
...@@ -12,7 +12,7 @@ export function getFormDefaultValues(address: string, tokenName: string, applica ...@@ -12,7 +12,7 @@ export function getFormDefaultValues(address: string, tokenName: string, applica
requester_name: application.requesterName, requester_name: application.requesterName,
requester_email: application.requesterEmail, requester_email: application.requesterEmail,
project_name: application.projectName, project_name: application.projectName,
project_sector: application.projectSector ? { value: application.projectSector, label: application.projectSector } : null, project_sector: application.projectSector ? [ { value: application.projectSector, label: application.projectSector } ] : null,
project_email: application.projectEmail, project_email: application.projectEmail,
project_website: application.projectWebsite, project_website: application.projectWebsite,
project_description: application.projectDescription || '', project_description: application.projectDescription || '',
...@@ -52,7 +52,7 @@ export function prepareRequestBody(data: Fields): Omit<TokenInfoApplication, 'id ...@@ -52,7 +52,7 @@ export function prepareRequestBody(data: Fields): Omit<TokenInfoApplication, 'id
projectDescription: data.project_description, projectDescription: data.project_description,
projectEmail: data.project_email, projectEmail: data.project_email,
projectName: data.project_name, projectName: data.project_name,
projectSector: data.project_sector?.value, projectSector: data.project_sector?.[0]?.value,
projectWebsite: data.project_website, projectWebsite: data.project_website,
reddit: data.reddit, reddit: data.reddit,
requesterEmail: data.requester_email, requesterEmail: data.requester_email,
......
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