Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 16 additions & 38 deletions src/components/forms/AccountAbstractionGrantsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
import { ACCOUNT_ABSTRACTION_GRANTS_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants';

import { AccountAbstractionGrantsFormData, ApplyingAs, GrantsReferralSource } from '../../types';
import { UploadFile } from './fields';

export const AccountAbstractionGrantsForm: FC = () => {
const router = useRouter();
Expand Down Expand Up @@ -92,6 +93,14 @@ export const AccountAbstractionGrantsForm: FC = () => {
setGrantsReferralSource(source);
};

const handleDrop = () => {
toast({
...TOAST_OPTIONS,
title: 'Proposal uploaded!',
status: 'success'
});
};

return (
<Stack
w='100%'
Expand Down Expand Up @@ -583,15 +592,15 @@ export const AccountAbstractionGrantsForm: FC = () => {
)}
</FormControl>

<FormControl id='website-control' isRequired mb={8}>
<FormLabel htmlFor='website' mb={1}>
<FormControl id='proposal-control' isRequired mb={8}>
<FormLabel htmlFor='proposal' mb={1}>
<PageText display='inline' fontSize='input'>
Grant Proposal URL
Grant Proposal
</PageText>
</FormLabel>

<PageText as='small' fontSize='helpText' color='brand.helpText'>
Please provide a link to your grant proposal for review.{' '}
Please upload a file with your grant proposal for review.{' '}
<Link
fontWeight={700}
color='brand.orange.200'
Expand All @@ -603,43 +612,12 @@ export const AccountAbstractionGrantsForm: FC = () => {
</Link>
</PageText>

<Box position='relative'>
<PageText fontSize='input' position='absolute' top='28.5px' left={4} zIndex={9}>
https://
</PageText>
<Input
id='website'
type='text'
placeholder='yourgrantproposal.com'
bg='white'
borderRadius={0}
borderColor='brand.border'
h='56px'
_placeholder={{ fontSize: 'input' }}
position='relative'
color='brand.paragraph'
fontSize='input'
pl={16}
mt={3}
{...register('website', {
required: true,
maxLength: 255
})}
/>
</Box>

{errors?.website?.type === 'maxLength' && (
<Box mt={1}>
<PageText as='small' fontSize='helpText' color='red.500'>
The URL cannot exceed 255 characters.
</PageText>
</Box>
)}
<UploadFile name='proposal' title='Upload proposal' onDrop={handleDrop} mt={4} />

{errors?.website?.type === 'required' && (
{errors?.proposal?.type === 'required' && (
<Box mt={1}>
<PageText as='small' fontSize='helpText' color='red.500'>
A URL is required.
A proposal file is required.
</PageText>
</Box>
)}
Expand Down
36 changes: 22 additions & 14 deletions src/components/forms/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,29 @@ export const api = {
},
accountAbstractionGrants: {
submit: (data: AccountAbstractionGrantsFormData) => {
const curatedData: { [key: string]: any } = {
...data,
applyingAs: data.applyingAs.value,
// Company is a required field in SF, we're using the Name as default value if no company provided
company: data.company === 'N/A' ? `${data.firstName} ${data.lastName}` : data.company,
country: data.country.value,
timezone: data.timezone.value,
projectCategory: data.projectCategory.value,
howDidYouHearAboutGrantsWave: data.howDidYouHearAboutGrantsWave.value,
wouldYouShareYourResearch: data.wouldYouShareYourResearch.value,
repeatApplicant: data.repeatApplicant === 'Yes',
canTheEFReachOut: data.canTheEFReachOut === 'Yes'
};

const formData = new FormData();

for (const name in data) {
formData.append(name, curatedData[name]);
}

const accountAbstractionRequestOptions: RequestInit = {
...methodOptions,
body: JSON.stringify({
...data,
applyingAs: data.applyingAs.value,
// Company is a required field in SF, we're using the Name as default value if no company provided
company: data.company === 'N/A' ? `${data.firstName} ${data.lastName}` : data.company,
country: data.country.value,
timezone: data.timezone.value,
projectCategory: data.projectCategory.value,
howDidYouHearAboutGrantsWave: data.howDidYouHearAboutGrantsWave.value,
wouldYouShareYourResearch: data.wouldYouShareYourResearch.value,
repeatApplicant: data.repeatApplicant === 'Yes',
canTheEFReachOut: data.canTheEFReachOut === 'Yes'
})
method: 'POST',
body: formData
};

return fetch(API_ACCOUNT_ABSTRACTION_GRANTS, accountAbstractionRequestOptions);
Expand Down
139 changes: 139 additions & 0 deletions src/components/forms/fields/UploadFile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { FC, MouseEvent, useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useDropzone } from 'react-dropzone';
import {
Box,
ChakraProps,
Flex,
FormControl,
FormLabel,
Grid,
GridItem,
Input,
InputGroup,
Stack
} from '@chakra-ui/react';
import Image from 'next/image';

import { PageText } from '../../UI';
import { RemoveIcon } from '../../UI/icons';
import { MAX_PROPOSAL_FILE_SIZE } from '../../../constants';

import uploadSVG from '../../../../public/images/upload.svg';

interface UploadFileProps extends ChakraProps {
name?: string;
title: string;
onDrop: (acceptedFiles: File[]) => void;
}

export const UploadFile: FC<UploadFileProps> = ({ name = 'upload', title, onDrop, ...rest }) => {
const { control, formState, setValue, getValues } = useFormContext();

const handleDrop = (acceptedFiles: File[]) => {
const file = acceptedFiles[0];
setValue(name, file, { shouldValidate: true });

onDrop(acceptedFiles);
};

const handleRemoveFile = (e: MouseEvent<HTMLInputElement>) => {
e.stopPropagation();

setValue(name, null, { shouldValidate: true });
};

const { getRootProps, getInputProps } = useDropzone({ onDrop: handleDrop });
const selectedFile = getValues(name);

const { errors } = formState;

return (
<Controller
name={name}
control={control}
rules={{
required: true,
validate: file => (file ? file.size < MAX_PROPOSAL_FILE_SIZE : true)
}}
render={({ field: { onChange } }) => (
<FormControl id={name} {...rest} {...getRootProps()}>
<InputGroup>
<Input
id={name}
type='file'
role='button'
aria-label='File Upload'
hidden
onChange={onChange}
{...getInputProps({ name: 'base64' })}
/>
<Box
w='100%'
cursor='pointer'
bgColor='brand.upload.bg'
justifyContent='space-evenly'
py={9}
px={{ base: 6, md: 16 }}
>
<Grid templateColumns='150px 1fr'>
<GridItem alignSelf='center'>
<Box mr={6} flexShrink={0}>
<Image src={uploadSVG} alt='Upload file' height={42} width={44} />
</Box>
</GridItem>
<GridItem mb={selectedFile ? 4 : 0}>
<Stack>
<FormLabel htmlFor={name}>
<PageText fontSize='input' fontWeight={700} mb={2}>
{title}
</PageText>
</FormLabel>

<PageText
as='small'
fontSize='helpText'
color='brand.helpText'
lineHeight='17px'
display='inline-block'
mb={2}
>
Click here or drag file to this box.
</PageText>
</Stack>

{selectedFile && errors[name] && (
<Box mt={1}>
<PageText as='small' fontSize='helpText' color='red.500'>
File size cannot exceed 4mb.
</PageText>
</Box>
)}
</GridItem>
<GridItem colStart={2}>
{selectedFile && (
<Flex
display='inline-flex'
alignItems='center'
justifyContent='space-between'
bg='brand.upload.filename'
minW='175px'
pl={4}
py={2}
borderRadius='5px'
>
<PageText mr={2}>{selectedFile.name}</PageText>
<Flex role='button' onClick={handleRemoveFile} px={3}>
<RemoveIcon />
</Flex>
</Flex>
)}
</GridItem>
</Grid>
</Box>
</InputGroup>
</FormControl>
)}
/>
);
};
1 change: 1 addition & 0 deletions src/components/forms/fields/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './Captcha';
export * from './UploadFile';
Loading