import React, { useEffect } from 'react'; import { getFormErrorLabelsProps, FormErrorLabelsProps } from './errorHandling'; import * as Yup from 'yup'; import { withProposalFormData, ProposalFormExportProps, ProposalFormContainerProps, ProposalFormInnerProps, genericFormDefaultOptions } from './GenericProposalForm'; import { GenericWorkingGroupProposalForm, FormValues as WGFormValues, defaultValues as wgFromDefaultValues } from './GenericWorkingGroupProposalForm'; import { FormField, InputFormField, TextareaFormField } from './FormFields'; import { withFormContainer } from './FormContainer'; import { ActivateOpeningAtKey, ActivateOpeningAtDef, StakingAmountLimitModeKeys, IApplicationRationingPolicy, IStakingPolicy } from '@joystream/types/hiring'; import { GenericJoyStreamRoleSchema } from '@joystream/types/hiring/schemas/role.schema.typings'; import { Dropdown, Grid, Message, Checkbox } from 'semantic-ui-react'; import { formatBalance } from '@polkadot/util'; import _ from 'lodash'; import { IWorkingGroupOpeningPolicyCommitment } from '@joystream/types/working-group'; import { IAddOpeningParameters } from '@joystream/types/proposals'; import { WorkingGroupKey, InputValidationLengthConstraint } from '@joystream/types/common'; import { BlockNumber } from '@polkadot/types/interfaces'; import { withCalls } from '@polkadot/react-api'; import { SimplifiedTypeInterface } from '@polkadot/joy-utils/types/common'; import Validation from '../validationSchema'; export type FormValues = WGFormValues & { activateAt: ActivateOpeningAtKey; activateAtBlock: string; maxReviewPeriodLength: string; applicationsLimited: boolean; maxApplications: string; applicationStakeRequired: boolean; applicationStakeMode: StakingAmountLimitModeKeys; applicationStakeValue: string; roleStakeRequired: boolean; roleStakeMode: StakingAmountLimitModeKeys; roleStakeValue: string; terminateRoleUnstakingPeriod: string; leaveRoleUnstakingPeriod: string; humanReadableText: string; }; const defaultValues: FormValues = { ...wgFromDefaultValues, activateAt: 'CurrentBlock', activateAtBlock: '', maxReviewPeriodLength: (14400 * 30).toString(), // 30 days applicationsLimited: false, maxApplications: '', applicationStakeRequired: false, applicationStakeMode: StakingAmountLimitModeKeys.Exact, applicationStakeValue: '', roleStakeRequired: false, roleStakeMode: StakingAmountLimitModeKeys.Exact, roleStakeValue: '', terminateRoleUnstakingPeriod: (14400 * 7).toString(), // 7 days leaveRoleUnstakingPeriod: (14400 * 7).toString(), // 7 days humanReadableText: '' }; const HRTDefault: (memberHandle: string, group: WorkingGroupKey) => GenericJoyStreamRoleSchema = (memberHandle, group) => ({ version: 1, headline: `Looking for ${group} Working Group Leader!`, job: { title: `${group} Working Group Leader`, description: `Become ${group} Working Group Leader! This is a great opportunity to support Joystream!` }, application: { sections: [ { title: 'About you', questions: [ { title: 'Your name', type: 'text' }, { title: 'What makes you a good fit for the job?', type: 'text area' } ] } ] }, reward: '100 JOY per block', creator: { membership: { handle: memberHandle } } }); type FormAdditionalProps = {}; // Aditional props coming all the way from export component into the inner form. type ExportComponentProps = ProposalFormExportProps; type FormContainerProps = ProposalFormContainerProps & { currentBlock?: BlockNumber; HRTConstraint?: InputValidationLengthConstraint; }; type FormInnerProps = ProposalFormInnerProps; type StakeFieldsProps = Pick & { errorLabelsProps: FormErrorLabelsProps; stakeType: 'role' | 'application'; }; const StakeFields: React.FunctionComponent = ({ values, errorLabelsProps, handleChange, stakeType, setFieldValue }) => { return ( <> { setFieldValue(`${stakeType}StakeRequired`, data.checked); }} label={ `Require ${stakeType} stake` } checked={ stakeType === 'role' ? values.roleStakeRequired : values.applicationStakeRequired }/> { (stakeType === 'role' ? values.roleStakeRequired : values.applicationStakeRequired) && (<> ({ text: mode, value: mode }))} value={ stakeType === 'role' ? values.roleStakeMode : values.applicationStakeMode } /> ) } ); }; const valuesToAddOpeningParams = (values: FormValues): SimplifiedTypeInterface => { const commitment: SimplifiedTypeInterface = { max_review_period_length: parseInt(values.maxReviewPeriodLength) }; if (parseInt(values.terminateRoleUnstakingPeriod) > 0) { commitment.terminate_role_stake_unstaking_period = parseInt(values.terminateRoleUnstakingPeriod); } if (parseInt(values.leaveRoleUnstakingPeriod) > 0) { commitment.exit_role_stake_unstaking_period = parseInt(values.leaveRoleUnstakingPeriod); } if (values.applicationsLimited) { const rationingPolicy: SimplifiedTypeInterface = { max_active_applicants: parseInt(values.maxApplications) }; commitment.application_rationing_policy = rationingPolicy; } if (values.applicationStakeRequired) { const applicationStakingPolicy: SimplifiedTypeInterface = { amount: parseInt(values.applicationStakeValue), amount_mode: values.applicationStakeMode }; commitment.application_staking_policy = applicationStakingPolicy; } if (values.roleStakeRequired) { const roleStakingPolicy: SimplifiedTypeInterface = { amount: parseInt(values.roleStakeValue), amount_mode: values.roleStakeMode }; commitment.role_staking_policy = roleStakingPolicy; } return { activate_at: { [values.activateAt]: values.activateAt === 'ExactBlock' ? parseInt(values.activateAtBlock) : null }, commitment: commitment, human_readable_text: values.humanReadableText, working_group: values.workingGroup }; }; const AddWorkingGroupOpeningForm: React.FunctionComponent = props => { const { handleChange, errors, touched, values, setFieldValue, myMemberId, myMembership } = props; useEffect(() => { if (myMembership && !touched.humanReadableText) { setFieldValue( 'humanReadableText', JSON.stringify(HRTDefault(myMembership.handle.toString(), values.workingGroup), undefined, 4) ); } }, [values.workingGroup, myMembership]); const errorLabelsProps = getFormErrorLabelsProps(errors, touched); return ( ({ text: wgKey, value: wgKey }))} value={values.activateAt} /> { values.activateAt === 'ExactBlock' && ( ) } { values.activateAt === 'ExactBlock' && ( In case ExactBlock is specified, the opening will remain in Waiting to Begin stage (which means it will be visible, but no applicants will be able to apply yet) until current block number will equal the specified number. ) } { setFieldValue('applicationsLimited', data.checked); }} label="Limit applications" checked={values.applicationsLimited}/> { values.applicationsLimited && ( ) } ); }; const FormContainer = withFormContainer({ mapPropsToValues: (props: FormContainerProps) => ({ ...defaultValues, ...(props.initialData || {}) }), validationSchema: (props: FormContainerProps) => Yup.object().shape({ ...genericFormDefaultOptions.validationSchema, ...Validation.AddWorkingGroupLeaderOpening( props.currentBlock?.toNumber() || 0, props.HRTConstraint ) }), handleSubmit: genericFormDefaultOptions.handleSubmit, displayName: 'AddWorkingGroupOpeningForm' })(AddWorkingGroupOpeningForm); export default withCalls( ['derive.chain.bestNumber', { propName: 'currentBlock' }], ['query.storageWorkingGroup.openingHumanReadableText', { propName: 'HRTConstraint' }] )( withProposalFormData(FormContainer) );