Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change proposal field to be a file uploader #279

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
@@ -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();
@@ -92,6 +93,14 @@ export const AccountAbstractionGrantsForm: FC = () => {
setGrantsReferralSource(source);
};

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

return (
<Stack
w='100%'
@@ -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'
@@ -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>
)}
36 changes: 22 additions & 14 deletions src/components/forms/api.ts
Original file line number Diff line number Diff line change
@@ -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);
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