Update add project page

This commit is contained in:
Hazem Krimi
2021-06-16 02:39:46 +01:00
parent bb3e7b00fd
commit 1a8f6be148
+817 -5
View File
@@ -14,15 +14,30 @@ import {
CategoryCard, CategoryCard,
FeatureCard, FeatureCard,
Input, Input,
SectionSelector,
Select,
Spinner, Spinner,
TemplateCard, TemplateCard,
Text, Text,
TextArea,
} from '../../components'; } from '../../components';
import { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from '../../assets'; import {
ArrowLeft,
ArrowRight,
ChevronLeft,
ChevronRight,
Profile,
Security,
} from '../../assets';
import { import {
AddProjectMutation, AddProjectMutation,
AddProjectMutationVariables, AddProjectMutationVariables,
AddProjectProposalMutation,
AddProjectProposalMutationVariables,
CategoryOutput, CategoryOutput,
CountryPrefixModel,
CreateUserMutation,
CreateUserMutationVariables,
DelivrableInput, DelivrableInput,
FeatureOutput, FeatureOutput,
GetAllCategoriesQuery, GetAllCategoriesQuery,
@@ -31,15 +46,22 @@ import {
GetAllFeaturesQueryVariables, GetAllFeaturesQueryVariables,
GetAllTemplatesByCategoriesIdQuery, GetAllTemplatesByCategoriesIdQuery,
GetAllTemplatesByCategoriesIdQueryVariables, GetAllTemplatesByCategoriesIdQueryVariables,
GetAllUsersQuery,
GetAllUsersQueryVariables,
GetCountryCodesQuery,
GetCountryCodesQueryVariables,
PaymentOptionInput, PaymentOptionInput,
ProjectInput, ProjectInput,
TemplateOutput, TemplateOutput,
UserOutput,
} from '../../graphql/types'; } from '../../graphql/types';
import { theme } from '../../themes'; import { theme } from '../../themes';
import { GET_ALL_CATEGORIES } from '../../graphql/category.api'; import { GET_ALL_CATEGORIES } from '../../graphql/category.api';
import { GET_ALL_TEMPLATES_BY_CATEGORIES_ID } from '../../graphql/template.api'; import { GET_ALL_TEMPLATES_BY_CATEGORIES_ID } from '../../graphql/template.api';
import { GET_ALL_FEATURES } from '../../graphql/feature.api'; import { GET_ALL_FEATURES } from '../../graphql/feature.api';
import { ADD_PROJECT } from '../../graphql/project.api'; import { ADD_PROJECT, ADD_PROJECT_PROPOSAL } from '../../graphql/project.api';
import { CREATE_USER, GET_ALL_USERS } from '../../graphql/admin.api';
import { GET_COUNTRY_CODES } from '../../graphql/auth.api';
const AddProject = () => { const AddProject = () => {
const history = useHistory(); const history = useHistory();
@@ -68,12 +90,66 @@ const AddProject = () => {
chosenDeliverables, chosenDeliverables,
setChosenDeliverables, setChosenDeliverables,
] = useState<DelivrableInput>(); ] = useState<DelivrableInput>();
const [, setChosenPaymentOption] = useState<PaymentOptionInput>(); const [
chosenPaymentOption,
setChosenPaymentOption,
] = useState<PaymentOptionInput>();
const [chosenPlatforms, setChosenPlatforms] = useState<Array<string>>([]); const [chosenPlatforms, setChosenPlatforms] = useState<Array<string>>([]);
const [selectedFeature, setSelectedFeature] = useState<FeatureOutput>(); const [selectedFeature, setSelectedFeature] = useState<FeatureOutput>();
const [categories, setCategories] = useState<Array<CategoryOutput>>([]); const [categories, setCategories] = useState<Array<CategoryOutput>>([]);
const [templates, setTemplates] = useState<Array<TemplateOutput>>([]); const [templates, setTemplates] = useState<Array<TemplateOutput>>([]);
const [features, setFeatures] = useState<Array<FeatureOutput>>([]); const [features, setFeatures] = useState<Array<FeatureOutput>>([]);
const [newUser, setNewUser] = useState<{
firstName: string;
lastName: string;
email: string;
password: string;
phone: {
prefix: string;
number: string;
};
address: {
place: string;
city: string;
country: string;
zip: string;
};
role: 'Client' | 'ProductOwner' | 'Developer';
}>({
firstName: '',
lastName: '',
email: '',
password: '',
phone: {
prefix: '',
number: '',
},
address: {
place: '',
city: '',
country: '',
zip: '',
},
role: 'Client',
});
const [selectedSection, setSelectedSection] = useState<
'general' | 'security'
>('general');
const [countryCodes, setCountryCodes] = useState<Array<CountryPrefixModel>>(
[]
);
const [client, setClient] = useState<UserOutput>();
const [proposal, setProposal] = useState<{
devtime: {
months: number;
days: number;
hours: number;
};
summary: string;
purpose: string;
resources: Array<{ resourceType: string; developers: number }>;
}>();
const [developers, setDevelopers] = useState<Array<UserOutput>>([]);
const [getCategories, { loading: categoriesLoading }] = useLazyQuery< const [getCategories, { loading: categoriesLoading }] = useLazyQuery<
GetAllCategoriesQuery, GetAllCategoriesQuery,
@@ -105,12 +181,65 @@ const AddProject = () => {
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
}); });
const [getDevelopers, { loading: developersLoading }] = useLazyQuery<
GetAllUsersQuery,
GetAllUsersQueryVariables
>(GET_ALL_USERS, {
onCompleted({ getAllUsers }) {
setDevelopers(getAllUsers.filter((user) => user.role === 'Developer'));
},
fetchPolicy: 'network-only',
});
const [createUser, { loading: createUserLoading }] = useMutation<
CreateUserMutation,
CreateUserMutationVariables
>(CREATE_USER, {
onCompleted({ createUser: createdUser }) {
setClient(createdUser);
setStep('project-metadata');
},
onError({ graphQLErrors }) {
setError(graphQLErrors[0]?.extensions?.info);
setTimeout(() => setError(''), 3000);
},
});
const [
addProjectProposal,
{ loading: addProjectProposalLoading },
] = useMutation<
AddProjectProposalMutation,
AddProjectProposalMutationVariables
>(ADD_PROJECT_PROPOSAL, {
onCompleted({ addProjectProposal: proposalData }) {
history.push(`/project/${proposalData.id}`);
},
onError({ graphQLErrors }) {
setError(graphQLErrors[0].extensions?.info);
setTimeout(() => setError(''), 3000);
},
});
const [addProject, { loading: addProjectLoading }] = useMutation< const [addProject, { loading: addProjectLoading }] = useMutation<
AddProjectMutation, AddProjectMutation,
AddProjectMutationVariables AddProjectMutationVariables
>(ADD_PROJECT, { >(ADD_PROJECT, {
onCompleted({ addProject: projectData }) { onCompleted({ addProject: projectData }) {
history.push(`/project/${projectData.id}`); if (role === 'client') history.push(`/project/${projectData.id}`);
else {
addProjectProposal({
variables: {
id: projectData.id,
proposal: {
devtime: proposal?.devtime!,
resources: proposal?.resources!,
summary: proposal?.summary!,
purpose: proposal?.summary!,
},
},
});
}
}, },
onError({ graphQLErrors }) { onError({ graphQLErrors }) {
setError(graphQLErrors[0].extensions?.info); setError(graphQLErrors[0].extensions?.info);
@@ -123,6 +252,8 @@ const AddProject = () => {
if (step === 'template') if (step === 'template')
getTemplates({ variables: { categories: chosenCategories } }); getTemplates({ variables: { categories: chosenCategories } });
if (step === 'features') getFeatures(); if (step === 'features') getFeatures();
if (step === 'client-creation') getCountryCodes();
if (step === 'project-metadata') getDevelopers();
// eslint-disable-next-line // eslint-disable-next-line
}, [step]); }, [step]);
@@ -280,6 +411,181 @@ const AddProject = () => {
}, },
}); });
const clientCreationGeneralForm = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
prefix: '',
number: '',
place: '',
city: '',
zip: '',
country: '',
},
validationSchema: Yup.object().shape({
firstName: Yup.string().required('First Name is required'),
lastName: Yup.string().required('Last Name is required'),
email: Yup.string()
.required('Email is required')
.email('Email is invalid'),
prefix: Yup.string().required('Prefix is required'),
// prettier-ignore
number: Yup.number().typeError('Phone must be a number').required('Phone is required'),
place: Yup.string().required('Address is required'),
city: Yup.string().required('City is required'),
country: Yup.string().required('Country is required'),
// prettier-ignore
zip: Yup.number().typeError('Zip must be a number').required('Zip is required'),
}),
onSubmit: ({
firstName,
lastName,
email,
prefix,
number,
place,
city,
country,
zip,
}) => {
setNewUser({
...newUser,
firstName,
lastName,
email,
phone: { prefix, number },
address: { place, city, country, zip },
});
setSelectedSection('security');
},
});
const [getCountryCodes, { loading: countryCodesLoading }] = useLazyQuery<
GetCountryCodesQuery,
GetCountryCodesQueryVariables
>(GET_COUNTRY_CODES, {
onCompleted({ getCountryCode }) {
setCountryCodes(getCountryCode);
clientCreationGeneralForm.setFieldValue(
'prefix',
getCountryCode[0].prefix
);
clientCreationGeneralForm.setFieldValue(
'country',
getCountryCode[0].country
);
},
fetchPolicy: 'network-only',
});
const clientCreationSecurityForm = useFormik({
initialValues: {
password: '',
confirmPassword: '',
},
validationSchema: Yup.object().shape({
password: Yup.string()
.required('Password is required')
.min(6, 'Password is 6 characters minimum'),
confirmPassword: Yup.string()
.required('Confirm password is required')
.oneOf(
[Yup.ref('password')],
"Confirm new password doesn't match with new password"
),
}),
onSubmit: ({ password }) => {
setNewUser({ ...newUser, password });
createUser({ variables: { user: { ...newUser, password } } });
},
});
const projectMetadataForm = useFormik<{
frontendDevelopers: Array<string>;
backendDevelopers: Array<string>;
months: string;
summary: string;
purpose: string;
}>({
initialValues: {
frontendDevelopers: [],
backendDevelopers: [],
months: '',
summary: '',
purpose: '',
},
validationSchema: Yup.object().shape({
summary: Yup.string().required('Summary is required'),
purpose: Yup.string().required('Purpose is required'),
// prettier-ignore
months: Yup.number().typeError('Months must be a number').required('Months is required'),
}),
onSubmit: ({
frontendDevelopers,
backendDevelopers,
months,
summary,
purpose,
}) => {
if (
!frontendDevelopers ||
frontendDevelopers.length === 0 ||
!backendDevelopers ||
backendDevelopers.length === 0
) {
setError('You must select developers for your project');
setTimeout(() => setError(''), 3000);
return;
}
setProposal({
...proposal,
devtime: {
months: parseInt(months, 10),
days: 0,
hours: 0,
},
resources: [
{
resourceType: 'frontend-developers',
developers: frontendDevelopers.length,
},
{
resourceType: 'backend-developers',
developers: backendDevelopers.length,
},
],
summary,
purpose,
});
addProject({
variables: {
project: {
name: project?.name!,
image: {
name: project?.image?.name!,
src: project?.image?.src!,
},
features: chosenFeatures.map((feature) => feature.id)!,
template: chosenTemplate?.id!,
clientId: client?.id!,
platforms: chosenPlatforms,
delivrable: chosenDeliverables,
paymentOption: {
optOne: chosenPaymentOption?.optOne!,
optTwo: chosenPaymentOption?.optTwo!,
optThree: chosenPaymentOption?.optThree!,
}!,
totalPrice: chosenFeatures.reduce(
(accumulator, feature) => accumulator + feature.price,
0
),
},
},
});
},
});
const carouselSettings = { const carouselSettings = {
pagination: false, pagination: false,
itemsToShow: 1, itemsToShow: 1,
@@ -346,6 +652,8 @@ const AddProject = () => {
{step === 'deliverables-platforms' && {step === 'deliverables-platforms' &&
'Choose Deliverables and Platforms'} 'Choose Deliverables and Platforms'}
{step === 'payment-options' && 'Choose Payment Options'} {step === 'payment-options' && 'Choose Payment Options'}
{step === 'client-creation' && 'Create client'}
{step === 'project-metadata' && 'Set project metadata'}
</Text> </Text>
</Box> </Box>
{error && <Alert color='error' text={error} />} {error && <Alert color='error' text={error} />}
@@ -368,6 +676,7 @@ const AddProject = () => {
if (step === 'deliverables-platforms') setStep('features'); if (step === 'deliverables-platforms') setStep('features');
if (step === 'payment-options') if (step === 'payment-options')
setStep('deliverables-platforms'); setStep('deliverables-platforms');
if (step === 'project-metadata') setStep('client-creation');
}} }}
/> />
</Box> </Box>
@@ -378,7 +687,11 @@ const AddProject = () => {
? 'Save' ? 'Save'
: 'Next' : 'Next'
} }
loading={addProjectLoading} loading={
role === 'client'
? addProjectLoading
: addProjectProposalLoading
}
color={role || 'client'} color={role || 'client'}
variant='primary-action' variant='primary-action'
iconRight={<ArrowRight />} iconRight={<ArrowRight />}
@@ -391,6 +704,10 @@ const AddProject = () => {
deliverablesPlatformsForm.handleSubmit(); deliverablesPlatformsForm.handleSubmit();
if (step === 'payment-options') if (step === 'payment-options')
paymentOptionsForm.handleSubmit(); paymentOptionsForm.handleSubmit();
if (step === 'client-creation')
clientCreationSecurityForm.handleSubmit();
if (step === 'project-metadata')
projectMetadataForm.handleSubmit();
}} }}
/> />
</Box> </Box>
@@ -1139,6 +1456,501 @@ const AddProject = () => {
</Box> </Box>
</form> </form>
)} )}
{step === 'client-creation' && (
<>
<Box
display='grid'
gridTemplateColumns='0.5fr 2fr'
columnGap='25px'
marginTop='1rem'
>
<Box display='grid' rowGap='0.5rem' gridTemplateRows='50px'>
<SectionSelector
icon={<Profile />}
color={role || 'client'}
text='General'
selected={selectedSection === 'general'}
/>
<SectionSelector
icon={<Security />}
color={role || 'client'}
text='Security'
selected={selectedSection === 'security'}
/>
</Box>
<Box
background='white'
boxShadow='1px 1px 10px 0px rgba(50, 59, 105, 0.25)'
borderRadius='10px'
width='100%'
padding='30px'
>
<Box
display='grid'
gridTemplateColumns='auto 1fr'
columnGap='1rem'
alignItems='center'
marginBottom='50px'
>
<Text variant='subheader' weight='bold'>
{selectedSection === 'general' ? 'General' : 'Security'}
</Text>
{error && <Alert color='error' text={error} />}
</Box>
{selectedSection === 'general' && (
<>
{!countryCodesLoading ? (
<Box
display='grid'
gridTemplateColumns='auto'
rowGap='0.5rem'
position='relative'
>
<Input
name='firstName'
label='First Name'
color={role || 'client'}
value={clientCreationGeneralForm.values.firstName}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
error={
clientCreationGeneralForm.touched.firstName &&
!!clientCreationGeneralForm.errors.firstName
}
errorMessage={
clientCreationGeneralForm.errors.firstName
}
/>
<Input
name='lastName'
label='Last Name'
color={role || 'client'}
value={clientCreationGeneralForm.values.lastName}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
error={
clientCreationGeneralForm.touched.lastName &&
!!clientCreationGeneralForm.errors.lastName
}
errorMessage={
clientCreationGeneralForm.errors.lastName
}
/>
<Input
name='email'
label='Email'
color={role || 'client'}
value={clientCreationGeneralForm.values.email}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
error={
clientCreationGeneralForm.touched.email &&
!!clientCreationGeneralForm.errors.email
}
errorMessage={clientCreationGeneralForm.errors.email}
/>
<Box
display='grid'
gridTemplateColumns='1fr 1.5fr'
columnGap='10px'
>
<Select
name='prefix'
label='Country Code'
color={role || 'client'}
options={
countryCodes
? countryCodes.map(({ prefix, country }) => ({
value: prefix,
label: `+${prefix} (${country})`,
}))
: [{ value: '216', label: '+216' }]
}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
value={clientCreationGeneralForm.values.prefix}
select={clientCreationGeneralForm.values.prefix}
error={
clientCreationGeneralForm.touched.prefix &&
!!clientCreationGeneralForm.errors.prefix
}
errorMessage={
clientCreationGeneralForm.errors.prefix
}
/>
<Input
name='number'
type='tel'
label='Phone'
color={role || 'client'}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
value={clientCreationGeneralForm.values.number}
error={
clientCreationGeneralForm.touched.number &&
!!clientCreationGeneralForm.errors.number
}
errorMessage={
clientCreationGeneralForm.errors.number
}
/>
</Box>
<Input
name='place'
label='Address'
color={role || 'client'}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
value={clientCreationGeneralForm.values.place}
error={
clientCreationGeneralForm.touched.place &&
!!clientCreationGeneralForm.errors.place
}
errorMessage={clientCreationGeneralForm.errors.place}
/>
<Input
name='city'
label='City'
color={role || 'client'}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
value={clientCreationGeneralForm.values.city}
error={
clientCreationGeneralForm.touched.city &&
!!clientCreationGeneralForm.errors.city
}
errorMessage={clientCreationGeneralForm.errors.city}
/>
<Box
display='grid'
gridTemplateColumns='2fr 1fr'
columnGap='10px'
>
<Select
name='country'
label='Country'
color={role || 'client'}
options={
countryCodes
? countryCodes.map(({ country }) => ({
value: country,
label: country,
}))
: [{ value: 'Tunisia', label: 'Tunisia' }]
}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
value={clientCreationGeneralForm.values.country}
select={clientCreationGeneralForm.values.country}
error={
clientCreationGeneralForm.touched.country &&
!!clientCreationGeneralForm.errors.country
}
errorMessage={
clientCreationGeneralForm.errors.country
}
/>
<Input
name='zip'
label='Zip Code'
color={role || 'client'}
onChange={clientCreationGeneralForm.handleChange}
onBlur={clientCreationGeneralForm.handleBlur}
value={clientCreationGeneralForm.values.zip}
error={
clientCreationGeneralForm.touched.zip &&
!!clientCreationGeneralForm.errors.zip
}
errorMessage={clientCreationGeneralForm.errors.zip}
/>
</Box>
<Box
marginTop='0.5rem'
display='grid'
gridTemplateColumns='repeat(2, auto)'
justifyContent='flex-end'
>
<Button
variant='primary-action'
color={role || 'client'}
text='Next'
onClick={clientCreationGeneralForm.handleSubmit}
type='submit'
/>
</Box>
</Box>
) : (
<Box
display='grid'
alignItems='center'
justifyContent='center'
>
<Spinner color={role || 'client'} />
</Box>
)}
</>
)}
{selectedSection === 'security' && (
<Box
display='grid'
gridTemplateColumns='auto'
rowGap='0.5rem'
position='relative'
>
<Input
name='password'
label='Password'
color={role || 'client'}
type='password'
value={clientCreationSecurityForm.values.password}
onChange={clientCreationSecurityForm.handleChange}
onBlur={clientCreationSecurityForm.handleBlur}
error={
clientCreationSecurityForm.touched.password &&
!!clientCreationSecurityForm.errors.password
}
errorMessage={clientCreationSecurityForm.errors.password}
/>
<Input
name='confirmPassword'
label='Confirm Password'
color={role || 'client'}
type='password'
value={clientCreationSecurityForm.values.confirmPassword}
onChange={clientCreationSecurityForm.handleChange}
onBlur={clientCreationSecurityForm.handleBlur}
error={
clientCreationSecurityForm.touched.confirmPassword &&
!!clientCreationSecurityForm.errors.confirmPassword
}
errorMessage={
clientCreationSecurityForm.errors.confirmPassword
}
/>
<Box
marginTop='0.5rem'
display='flex'
justifyContent='flex-end'
>
<Box
marginRight='15px'
display='flex'
alignItems='center'
>
<Button
color={role || 'client'}
text='Previous'
type='submit'
onClick={() => setSelectedSection('general')}
/>
</Box>
<Button
variant='primary-action'
color={role || 'client'}
text='Create'
type='submit'
onClick={clientCreationSecurityForm.handleSubmit}
loading={createUserLoading}
disabled={createUserLoading}
/>
</Box>
</Box>
)}
</Box>
</Box>
</>
)}
{step === 'project-metadata' && (
<>
{!developersLoading ? (
<form>
<Box>
<Box marginBottom='20px'>
<Text variant='headline' weight='bold' gutterBottom>
Assign frontend Developers
</Text>
</Box>
<Box
display='grid'
gridTemplateColumns='repeat(4, 1fr)'
alignItems='center'
columnGap='30px'
marginBottom='20px'
>
{developers &&
developers.map((developer) => (
<Box
padding='10px'
background='white'
boxShadow='1px 1px 10px rgba(50, 59, 105, 0.25)'
onClick={() => {
if (
projectMetadataForm.values.frontendDevelopers &&
!projectMetadataForm.values.frontendDevelopers.includes(
developer.id
)
) {
projectMetadataForm.setFieldValue(
'frontendDevelopers',
projectMetadataForm.values.frontendDevelopers.concat(
developer.id
)
);
} else {
projectMetadataForm.setFieldValue(
'frontendDevelopers',
projectMetadataForm.values.frontendDevelopers &&
projectMetadataForm.values.frontendDevelopers.filter(
(id) => id !== developer.id
)
);
}
}}
border={
projectMetadataForm.values.frontendDevelopers.includes(
developer.id
)
? `2px solid ${
theme.colors[role || 'client'].main
}`
: undefined
}
display='grid'
gridTemplateRows='auto'
alignItems='center'
rowGap='10px'
borderRadius='10px'
cursor='pointer'
>
<Text variant='title' weight='bold'>
{developer.firstName} {developer.lastName}
</Text>
</Box>
))}
</Box>
</Box>
<Box>
<Box marginBottom='20px'>
<Text variant='headline' weight='bold' gutterBottom>
Assign backend Developers
</Text>
</Box>
<Box
display='grid'
gridTemplateColumns='repeat(4, 1fr)'
alignItems='center'
columnGap='30px'
marginBottom='20px'
>
{developers &&
developers.map((developer) => (
<Box
padding='10px'
background='white'
boxShadow='1px 1px 10px rgba(50, 59, 105, 0.25)'
onClick={() => {
if (
projectMetadataForm.values.backendDevelopers &&
!projectMetadataForm.values.backendDevelopers.includes(
developer.id
)
) {
projectMetadataForm.setFieldValue(
'backendDevelopers',
projectMetadataForm.values.backendDevelopers.concat(
developer.id
)
);
} else {
projectMetadataForm.setFieldValue(
'backendDevelopers',
projectMetadataForm.values.backendDevelopers &&
projectMetadataForm.values.backendDevelopers.filter(
(id) => id !== developer.id
)
);
}
}}
border={
projectMetadataForm.values.backendDevelopers.includes(
developer.id
)
? `2px solid ${
theme.colors[role || 'client'].main
}`
: undefined
}
display='grid'
gridTemplateRows='auto'
alignItems='center'
rowGap='10px'
borderRadius='10px'
cursor='pointer'
>
<Text variant='title' weight='bold'>
{developer.firstName} {developer.lastName}
</Text>
</Box>
))}
</Box>
</Box>
<Box>
<Box marginBottom='20px'>
<Text variant='headline' weight='bold' gutterBottom>
Define metadata
</Text>
</Box>
<Box
display='grid'
gridTemplateColumns='1fr'
alignItems='center'
rowGap='30px'
marginBottom='20px'
>
<TextArea
name='purpose'
label='Purpose'
value={projectMetadataForm.values.purpose}
onChange={projectMetadataForm.handleChange}
onBlur={projectMetadataForm.handleBlur}
error={
projectMetadataForm.touched.purpose &&
!!projectMetadataForm.errors.purpose
}
errorMessage={projectMetadataForm.errors.purpose}
/>
<TextArea
name='summary'
label='Summary'
value={projectMetadataForm.values.summary}
onChange={projectMetadataForm.handleChange}
onBlur={projectMetadataForm.handleBlur}
error={
projectMetadataForm.touched.summary &&
!!projectMetadataForm.errors.summary
}
errorMessage={projectMetadataForm.errors.summary}
/>
<Input
name='months'
label='Months'
value={projectMetadataForm.values.months}
onChange={projectMetadataForm.handleChange}
onBlur={projectMetadataForm.handleBlur}
error={
projectMetadataForm.touched.months &&
!!projectMetadataForm.errors.months
}
errorMessage={projectMetadataForm.errors.months}
/>
</Box>
</Box>
</form>
) : (
<Spinner fullScreen color={role} />
)}
</>
)}
</Box> </Box>
</Wrapper> </Wrapper>
); );