123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- 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<FormAdditionalProps, FormValues>;
- type FormContainerProps = ProposalFormContainerProps<ExportComponentProps> & {
- currentBlock?: BlockNumber;
- HRTConstraint?: InputValidationLengthConstraint;
- };
- type FormInnerProps = ProposalFormInnerProps<FormContainerProps, FormValues>;
- type StakeFieldsProps = Pick<FormInnerProps, 'values' | 'handleChange' | 'setFieldValue'> & {
- errorLabelsProps: FormErrorLabelsProps<FormValues>;
- stakeType: 'role' | 'application';
- };
- const StakeFields: React.FunctionComponent<StakeFieldsProps> = ({
- values,
- errorLabelsProps,
- handleChange,
- stakeType,
- setFieldValue
- }) => {
- return (
- <>
- <FormField label={`${_.startCase(stakeType)} stake` }>
- <Checkbox
- toggle
- onChange={(e, data) => { setFieldValue(`${stakeType}StakeRequired`, data.checked); }}
- label={ `Require ${stakeType} stake` }
- checked={ stakeType === 'role' ? values.roleStakeRequired : values.applicationStakeRequired }/>
- </FormField>
- { (stakeType === 'role' ? values.roleStakeRequired : values.applicationStakeRequired) && (<>
- <FormField label="Stake mode">
- <Dropdown
- onChange={handleChange}
- name={ `${stakeType}StakeMode` }
- selection
- options={[StakingAmountLimitModeKeys.Exact, StakingAmountLimitModeKeys.AtLeast].map(mode => ({ text: mode, value: mode }))}
- value={ stakeType === 'role' ? values.roleStakeMode : values.applicationStakeMode }
- />
- </FormField>
- <InputFormField
- label="Stake value"
- unit={formatBalance.getDefaults().unit}
- onChange={handleChange}
- name={ `${stakeType}StakeValue` }
- error={ stakeType === 'role' ? errorLabelsProps.roleStakeValue : errorLabelsProps.applicationStakeValue}
- value={ stakeType === 'role' ? values.roleStakeValue : values.applicationStakeValue}
- placeholder={'ie. 100'}
- />
- </>) }
- </>
- );
- };
- const valuesToAddOpeningParams = (values: FormValues): SimplifiedTypeInterface<IAddOpeningParameters> => {
- const commitment: SimplifiedTypeInterface<IWorkingGroupOpeningPolicyCommitment> = {
- 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<IApplicationRationingPolicy> = {
- max_active_applicants: parseInt(values.maxApplications)
- };
- commitment.application_rationing_policy = rationingPolicy;
- }
- if (values.applicationStakeRequired) {
- const applicationStakingPolicy: SimplifiedTypeInterface<IStakingPolicy> = {
- amount: parseInt(values.applicationStakeValue),
- amount_mode: values.applicationStakeMode
- };
- commitment.application_staking_policy = applicationStakingPolicy;
- }
- if (values.roleStakeRequired) {
- const roleStakingPolicy: SimplifiedTypeInterface<IStakingPolicy> = {
- 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<FormInnerProps> = 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<FormValues>(errors, touched);
- return (
- <GenericWorkingGroupProposalForm
- {...props}
- txMethod="createAddWorkingGroupLeaderOpeningProposal"
- proposalType="AddWorkingGroupLeaderOpening"
- submitParams={[
- myMemberId,
- values.title,
- values.rationale,
- '{STAKE}',
- valuesToAddOpeningParams(values)
- ]}
- >
- <Grid columns="4" doubling stackable verticalAlign="bottom">
- <Grid.Row>
- <Grid.Column>
- <FormField label="Activate opening at">
- <Dropdown
- onChange={handleChange}
- name="activateAt"
- selection
- options={Object.keys(ActivateOpeningAtDef).map(wgKey => ({ text: wgKey, value: wgKey }))}
- value={values.activateAt}
- />
- </FormField>
- </Grid.Column>
- <Grid.Column>
- { values.activateAt === 'ExactBlock' && (
- <InputFormField
- onChange={handleChange}
- name="activateAtBlock"
- error={errorLabelsProps.activateAtBlock}
- value={values.activateAtBlock}
- placeholder={'Provide the block number'}
- />
- ) }
- </Grid.Column>
- </Grid.Row>
- </Grid>
- { values.activateAt === 'ExactBlock' && (
- <Message info>
- In case <b>ExactBlock</b> is specified, the opening will remain in <i>Waiting to Begin</i> 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.
- </Message>
- ) }
- <Grid columns="4" doubling stackable verticalAlign="bottom">
- <Grid.Row>
- <Grid.Column>
- <InputFormField
- label="Max. review period"
- onChange={handleChange}
- name="maxReviewPeriodLength"
- error={errorLabelsProps.maxReviewPeriodLength}
- value={values.maxReviewPeriodLength}
- placeholder={'ie. 72000'}
- unit="blocks"
- />
- </Grid.Column>
- </Grid.Row>
- </Grid>
- <Grid columns="4" doubling stackable verticalAlign="bottom">
- <Grid.Row>
- <Grid.Column>
- <FormField label="Applications limit">
- <Checkbox
- toggle
- onChange={(e, data) => { setFieldValue('applicationsLimited', data.checked); }}
- label="Limit applications"
- checked={values.applicationsLimited}/>
- </FormField>
- { values.applicationsLimited && (
- <InputFormField
- onChange={handleChange}
- name="maxApplications"
- error={errorLabelsProps.maxApplications}
- value={values.maxApplications}
- placeholder={'Max. number of applications'}
- />
- ) }
- </Grid.Column>
- </Grid.Row>
- </Grid>
- <Grid columns="2" stackable style={{ marginBottom: 0 }}>
- <Grid.Row>
- <Grid.Column>
- <StakeFields stakeType="application" {...{ errorLabelsProps, values, handleChange, setFieldValue }}/>
- </Grid.Column>
- <Grid.Column>
- <StakeFields stakeType="role" {...{ errorLabelsProps, values, handleChange, setFieldValue }}/>
- </Grid.Column>
- </Grid.Row>
- </Grid>
- <Grid columns="2" stackable style={{ marginBottom: 0 }}>
- <Grid.Row>
- <Grid.Column>
- <InputFormField
- onChange={handleChange}
- name="terminateRoleUnstakingPeriod"
- error={errorLabelsProps.terminateRoleUnstakingPeriod}
- value={values.terminateRoleUnstakingPeriod}
- label={'Terminate role unstaking period'}
- placeholder={'ie. 14400'}
- unit="blocks"
- help={
- 'In case leader role or application is terminated - this will be the unstaking period for the role stake (in blocks).'
- }
- />
- </Grid.Column>
- <Grid.Column>
- <InputFormField
- onChange={handleChange}
- name="leaveRoleUnstakingPeriod"
- error={errorLabelsProps.leaveRoleUnstakingPeriod}
- value={values.leaveRoleUnstakingPeriod}
- label={'Leave role unstaking period'}
- placeholder={'ie. 14400'}
- unit="blocks"
- help={
- 'In case leader leaves/exits his role - this will be the unstaking period for his role stake (in blocks). ' +
- 'It also applies when user is withdrawing an active leader application.'
- }
- />
- </Grid.Column>
- </Grid.Row>
- </Grid>
- <TextareaFormField
- label="Opening schema (human_readable_text)"
- help="JSON schema that describes some characteristics of the opening presented in the UI (headers, content, application form etc.)"
- onChange={handleChange}
- name="humanReadableText"
- placeholder="Paste the JSON schema here..."
- error={errorLabelsProps.humanReadableText}
- value={values.humanReadableText}
- rows={20}
- />
- </GenericWorkingGroupProposalForm>
- );
- };
- const FormContainer = withFormContainer<FormContainerProps, FormValues>({
- 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<ExportComponentProps>(
- ['derive.chain.bestNumber', { propName: 'currentBlock' }],
- ['query.storageWorkingGroup.openingHumanReadableText', { propName: 'HRTConstraint' }]
- )(
- withProposalFormData<FormContainerProps, ExportComponentProps>(FormContainer)
- );
|