Feat/800: Updated and built more reusable proposal form components

This commit is contained in:
sam-keen
2022-09-05 22:49:12 +01:00
parent 52b1c24070
commit 2cdfb63050
11 changed files with 384 additions and 0 deletions
@@ -0,0 +1,10 @@
export * from './proposal-form-subheader';
export * from './proposal-form-min-requirements';
export * from './proposal-form-title';
export * from './proposal-form-description';
export * from './proposal-form-terms';
export * from './proposal-form-reference';
export * from './proposal-form-submit';
export * from './proposal-form-transaction-dialog';
export * from './proposal-form-vote-deadline';
export * from './proposal-form-enactment-deadline';
@@ -0,0 +1,32 @@
import { useTranslation } from 'react-i18next';
import { FormGroup, InputError, TextArea } from '@vegaprotocol/ui-toolkit';
import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormDescriptionProps {
registerField: UseFormRegisterReturn<'proposalDescription'>;
errorMessage: string | undefined;
}
export const ProposalFormDescription = function ({
registerField: register,
errorMessage,
}: ProposalFormDescriptionProps) {
const { t } = useTranslation();
return (
<FormGroup
label={t('ProposalDescription')}
labelDescription={t('ProposalDescriptionText')}
labelFor="proposal-description"
>
<TextArea
id="proposal-description"
maxLength={20000}
className="min-h-[200px]"
hasError={Boolean(errorMessage)}
data-testid="proposal-description"
{...register}
/>
{errorMessage && <InputError intent="danger">{errorMessage}</InputError>}
</FormGroup>
);
};
@@ -0,0 +1,77 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { parse as ISO8601Parse, toSeconds } from 'iso8601-duration';
import {
ButtonLink,
FormGroup,
Input,
InputError,
} from '@vegaprotocol/ui-toolkit';
import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormEnactmentDeadlineProps {
register: UseFormRegisterReturn<'proposalEnactmentDeadline'>;
errorMessage: string | undefined;
minEnact: string;
maxEnact: string;
}
export const ProposalFormEnactmentDeadline = function ({
register,
errorMessage,
minEnact,
maxEnact,
}: ProposalFormEnactmentDeadlineProps) {
const minEnactSeconds = toSeconds(
ISO8601Parse(`PT${minEnact.toUpperCase()}`)
);
const maxEnactSeconds = toSeconds(
ISO8601Parse(`PT${maxEnact.toUpperCase()}`)
);
// As we're rounding to hours for the simplified deadline ui presently, if deadline
// is less than one hour, make it one hour.
const minEnactHours =
Math.floor(minEnactSeconds / 3600) > 1
? Math.floor(minEnactSeconds / 3600)
: 1;
const maxEnactHours = Math.floor(maxEnactSeconds / 3600);
const [inputValue, setInputValue] = useState<number>(minEnactHours);
const { t } = useTranslation();
return (
<FormGroup
label={t('ProposalEnactmentDeadline')}
labelFor="proposal-enactment-deadline"
>
<div className="flex items-center gap-2">
<div className="relative w-28">
<Input
{...register}
id="proposal-enactment-deadline"
type="number"
value={inputValue}
min={minEnactHours}
max={maxEnactHours}
onChange={(e) => setInputValue(Number(e.target.value))}
data-testid="proposal-enactment-deadline"
className="pr-12"
/>
<span className="absolute right-2 bottom-1/2 translate-y-1/2 text-sm">
{t('Hours')}
</span>
</div>
<div className="flex items-center gap-4 text-sm">
<ButtonLink onClick={() => setInputValue(minEnactHours)}>
Use min
</ButtonLink>
<ButtonLink onClick={() => setInputValue(maxEnactHours)}>
Use max
</ButtonLink>
</div>
</div>
{errorMessage && <InputError intent="danger">{errorMessage}</InputError>}
</FormGroup>
);
};
@@ -0,0 +1,16 @@
import { useTranslation } from 'react-i18next';
interface ProposalFormMinRequirementsProps {
value?: string;
}
export const ProposalFormMinRequirements = ({
value = '1',
}: ProposalFormMinRequirementsProps) => {
const { t } = useTranslation();
return (
<p className="mb-4">{`${t('MinProposalRequirements1')} ${value} ${t(
'MinProposalRequirements2'
)}`}</p>
);
};
@@ -0,0 +1,27 @@
import { useTranslation } from 'react-i18next';
import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormReferenceProps {
registerField: UseFormRegisterReturn<'proposalReference'>;
errorMessage: string | undefined;
}
export const ProposalFormReference = function ({
registerField: register,
errorMessage,
}: ProposalFormReferenceProps) {
const { t } = useTranslation();
return (
<FormGroup label={t('ProposalReference')} labelFor="proposal-reference">
<Input
id="proposal-reference"
maxLength={100}
hasError={Boolean(errorMessage)}
data-testid="proposal-reference"
{...register}
/>
{errorMessage && <InputError intent="danger">{errorMessage}</InputError>}
</FormGroup>
);
};
@@ -0,0 +1,9 @@
import type { ReactNode } from 'react';
interface ProposalFormSubheaderProps {
children: ReactNode;
}
export const ProposalFormSubheader = ({
children,
}: ProposalFormSubheaderProps) => <h2 className="mt-8 mb-4">{children}</h2>;
@@ -0,0 +1,24 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@vegaprotocol/ui-toolkit';
interface ProposalFormSubmitProps {
isSubmitting: boolean;
}
export const ProposalFormSubmit = ({
isSubmitting,
}: ProposalFormSubmitProps) => {
const { t } = useTranslation();
return (
<span className="my-20">
<Button
variant="primary"
type="submit"
data-testid="proposal-submit"
disabled={isSubmitting}
>
{isSubmitting ? t('Submitting') : t('Submit')} {t('Proposal')}
</Button>
</span>
);
};
@@ -0,0 +1,48 @@
import { useTranslation } from 'react-i18next';
import {
FormGroup,
InputError,
Link,
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { Links } from '../../../../config';
import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormTermsProps {
registerField: UseFormRegisterReturn<'proposalTerms'>;
errorMessage: string | undefined;
labelOverride?: string;
}
export const ProposalFormTerms = function ({
registerField: register,
errorMessage,
labelOverride,
}: ProposalFormTermsProps) {
const { t } = useTranslation();
return (
<FormGroup
label={labelOverride || t('ProposalTerms')}
labelFor="proposal-terms"
>
<div className="mt-[-4px] mb-2 text-sm font-light">
{`${t('ProposalTermsText')} `}
<Link
href={Links.PROPOSALS_GUIDE}
target="_blank"
className="underline"
>
proposals guide
</Link>
</div>
<TextArea
id="proposal-terms"
className="min-h-[200px]"
hasError={Boolean(errorMessage)}
data-testid="proposal-terms"
{...register}
/>
{errorMessage && <InputError intent="danger">{errorMessage}</InputError>}
</FormGroup>
);
};
@@ -0,0 +1,31 @@
import { useTranslation } from 'react-i18next';
import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormTitleProps {
registerField: UseFormRegisterReturn<'proposalTitle'>;
errorMessage: string | undefined;
}
export const ProposalFormTitle = function ({
registerField: register,
errorMessage,
}: ProposalFormTitleProps) {
const { t } = useTranslation();
return (
<FormGroup
label={t('ProposalTitle')}
labelFor="proposal-title"
labelDescription={t('ProposalTitleText')}
>
<Input
id="proposal-title"
maxLength={100}
hasError={Boolean(errorMessage)}
data-testid="proposal-title"
{...register}
/>
{errorMessage && <InputError intent="danger">{errorMessage}</InputError>}
</FormGroup>
);
};
@@ -0,0 +1,37 @@
import {
getProposalDialogIcon,
getProposalDialogIntent,
getProposalDialogTitle,
} from '@vegaprotocol/governance';
import { t } from '@vegaprotocol/react-helpers';
import { ProposalState } from '@vegaprotocol/types';
import type { ProposalEvent_busEvents_event_Proposal } from '@vegaprotocol/governance';
import type { DialogProps } from '@vegaprotocol/wallet';
interface ProposalFormTransactionDialogProps {
finalizedProposal: ProposalEvent_busEvents_event_Proposal | null;
TransactionDialog: (props: DialogProps) => JSX.Element;
}
export const ProposalFormTransactionDialog = ({
finalizedProposal,
TransactionDialog,
}: ProposalFormTransactionDialogProps) => (
<div data-testid="proposal-transaction-dialog">
{finalizedProposal?.rejectionReason ? (
<TransactionDialog
title={t('Proposal rejected')}
intent={getProposalDialogIntent(ProposalState.STATE_REJECTED)}
icon={getProposalDialogIcon(ProposalState.STATE_REJECTED)}
>
<p>{finalizedProposal.rejectionReason}</p>
</TransactionDialog>
) : (
<TransactionDialog
title={getProposalDialogTitle(finalizedProposal?.state)}
intent={getProposalDialogIntent(finalizedProposal?.state)}
icon={getProposalDialogIcon(finalizedProposal?.state)}
/>
)}
</div>
);
@@ -0,0 +1,73 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { parse as ISO8601Parse, toSeconds } from 'iso8601-duration';
import {
ButtonLink,
FormGroup,
Input,
InputError,
} from '@vegaprotocol/ui-toolkit';
import type { UseFormRegisterReturn } from 'react-hook-form';
export interface ProposalFormVoteDeadlineProps {
register: UseFormRegisterReturn<'proposalVoteDeadline'>;
errorMessage: string | undefined;
minClose: string;
maxClose: string;
}
export const ProposalFormVoteDeadline = function ({
register,
errorMessage,
minClose,
maxClose,
}: ProposalFormVoteDeadlineProps) {
const minVoteSeconds = toSeconds(ISO8601Parse(`PT${minClose.toUpperCase()}`));
const maxVoteSeconds = toSeconds(ISO8601Parse(`PT${maxClose.toUpperCase()}`));
// As we're rounding to hours for the simplified deadline ui presently, if deadline
// is less than one hour, make it one hour.
const minVoteHours =
Math.floor(minVoteSeconds / 3600) > 1
? Math.floor(minVoteSeconds / 3600)
: 1;
const maxVoteHours = Math.floor(maxVoteSeconds / 3600);
const [inputValue, setInputValue] = useState<number>(minVoteHours);
const { t } = useTranslation();
return (
<FormGroup
label={t('ProposalVoteDeadline')}
labelFor="proposal-vote-deadline"
>
<div className="flex items-center gap-2">
<div className="relative w-28">
<Input
{...register}
id="proposal-vote-deadline"
type="number"
value={inputValue}
min={minVoteHours}
max={maxVoteHours}
onChange={(e) => setInputValue(Number(e.target.value))}
data-testid="proposal-vote-deadline"
className="pr-12"
/>
<span className="absolute right-2 bottom-1/2 translate-y-1/2 text-sm">
{t('Hours')}
</span>
</div>
<div className="flex items-center gap-4 text-sm">
<ButtonLink onClick={() => setInputValue(minVoteHours)}>
Use min
</ButtonLink>
<ButtonLink onClick={() => setInputValue(maxVoteHours)}>
Use max
</ButtonLink>
</div>
</div>
{errorMessage && <InputError intent="danger">{errorMessage}</InputError>}
</FormGroup>
);
};