Feat/800: Updated and built more reusable proposal form components
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
+77
@@ -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>
|
||||
);
|
||||
};
|
||||
+16
@@ -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>
|
||||
);
|
||||
};
|
||||
+37
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user