feat(2211): download proposal forms data as json (#2521)

* feat(2211): view proposal forms data as json

* feat(2211): removed the vega wallet barrier to viewing proposal forms

* feat(2211): tweaked the json viewing to download the proposal form json, with a useful title (proposal title and readable datetime)

* feat(2211): removed unwanted NX_SENTRY_DSN config from dev environments

* feat(2211): unit test for download-json.ts

* feat(2211): unit test for proposal-form-download-json.spec.tsx

* feat(2211): simplified unit test for proposal-form-download-json.spec.tsx

* feat(2211): removed unneeded unit test

* feat(2211): revoked change that was causing test failures
This commit is contained in:
Sam Keen 2023-01-09 09:48:47 +00:00 committed by GitHub
parent 863c288e0d
commit c5b72e5a64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 978 additions and 766 deletions

View File

@ -6,4 +6,3 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996

View File

@ -9,4 +9,3 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996

View File

@ -159,8 +159,10 @@
"Tokens are held in different <trancheLink>Tranches</trancheLink>. Each tranche has its own schedule for how the tokens are unlocked.": "Tokens are held in different <trancheLink>Tranches</trancheLink>. Each tranche has its own schedule for how the tokens are unlocked.",
"proposals": "Proposals",
"proposal": "Proposal",
"submitting": "Submitting",
"submittingProposal": "Submitting proposal",
"submit": "Submit",
"submitProposal": "Submit proposal",
"connectWalletToSubmitProposal": "Connect your wallet to submit a proposal",
"proposedEnactment": "Proposed enactment",
"Enacted": "Enacted",
"enactedOn": "Enacted on",
@ -729,5 +731,6 @@
"homeRewardsIntro": "Track rewards you've earned for trading, liquidity provision, market creation, and staking.",
"homeRewardsButtonText": "See rewards",
"homeVegaTokenIntro": "VEGA Token is a governance asset used to make and vote on proposals, and nominate validators.",
"homeVegaTokenButtonText": "Manage tokens"
"homeVegaTokenButtonText": "Manage tokens",
"downloadProposalJson": "Download proposal as JSON"
}

View File

@ -0,0 +1,26 @@
export const downloadJson = (jsonString: string, proposalTitle: string) => {
try {
const now = new Date();
const day = now.getDate().toString().padStart(2, '0');
const month = now.toLocaleString('en-US', { month: 'short' });
const year = now.getFullYear().toString();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
// e.g. "2023-Jan-03-23-59-59"
const formattedDateTime = `${day}-${month}-${year}-${hours}-${minutes}-${seconds}`;
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `${proposalTitle}-${formattedDateTime}.json`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.log(error);
}
};

View File

@ -5,3 +5,4 @@ export * from './proposal-form-terms';
export * from './proposal-form-submit';
export * from './proposal-form-transaction-dialog';
export * from './proposal-form-vote-and-enactment-deadline';
export * from './proposal-form-download-json';

View File

@ -0,0 +1,12 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { ProposalFormDownloadJson } from './proposal-form-download-json';
describe('ProposalFormDownloadJson', () => {
it('calls the downloadJson method when the button is clicked', () => {
const downloadJson = jest.fn();
render(<ProposalFormDownloadJson downloadJson={downloadJson} />);
const button = screen.getByTestId('proposal-download-json');
fireEvent.click(button);
expect(downloadJson).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,19 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@vegaprotocol/ui-toolkit';
interface ProposalFormDownloadJsonProps {
downloadJson: () => void;
}
export const ProposalFormDownloadJson = ({
downloadJson,
}: ProposalFormDownloadJsonProps) => {
const { t } = useTranslation();
return (
<div className="mb-6">
<Button data-testid="proposal-download-json" onClick={downloadJson}>
{t('downloadProposalJson')}
</Button>
</div>
);
};

View File

@ -0,0 +1,59 @@
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalFormSubmit } from './proposal-form-submit';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
const renderComponent = (
context: VegaWalletContextShape,
isSubmitting: boolean
) => {
render(
<AppStateProvider>
<VegaWalletContext.Provider value={context}>
<ProposalFormSubmit isSubmitting={isSubmitting} />
</VegaWalletContext.Provider>
</AppStateProvider>
);
};
describe('Proposal Form Submit', () => {
it('should display connection message and button if wallet not connected', () => {
renderComponent({ pubKey: null } as VegaWalletContextShape, false);
expect(
screen.getByText('Connect your wallet to submit a proposal')
).toBeInTheDocument();
expect(
screen.getByTestId('connect-to-vega-wallet-btn')
).toBeInTheDocument();
});
it('should display submit button if wallet is connected', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' };
renderComponent(
{
pubKey: pubKey.publicKey,
pubKeys: [pubKey],
} as VegaWalletContextShape,
false
);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submit proposal'
);
});
it('should display submitting button text if wallet is connected and submitting', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' };
renderComponent(
{
pubKey: pubKey.publicKey,
pubKeys: [pubKey],
} as VegaWalletContextShape,
true
);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submitting proposal'
);
});
});

View File

@ -1,5 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
interface ProposalFormSubmitProps {
isSubmitting: boolean;
@ -8,17 +10,25 @@ interface ProposalFormSubmitProps {
export const ProposalFormSubmit = ({
isSubmitting,
}: ProposalFormSubmitProps) => {
const { pubKey } = useVegaWallet();
const { t } = useTranslation();
return (
<div className="mt-10 my-20">
<Button
variant="primary"
type="submit"
data-testid="proposal-submit"
disabled={isSubmitting}
>
{isSubmitting ? t('Submitting') : t('Submit')} {t('Proposal')}
</Button>
<div className="mb-6">
<div className="mb-4 font-bold uppercase text-vega-orange">
{!pubKey && t('connectWalletToSubmitProposal')}
</div>
<VegaWalletContainer>
{() => (
<Button
variant="primary"
type="submit"
data-testid="proposal-submit"
disabled={isSubmitting}
>
{isSubmitting ? t('submittingProposal') : t('submitProposal')}
</Button>
)}
</VegaWalletContainer>
</div>
);
};

View File

@ -437,7 +437,7 @@ export function ProposalFormVoteAndEnactmentDeadline({
const { t } = useTranslation();
return (
<>
<div className="mb-10">
<ProposalFormSubheader>
{enactmentRegister && enactmentMinClose && enactmentMaxClose
? t('ProposalVoteAndEnactmentTitle')
@ -535,6 +535,6 @@ export function ProposalFormVoteAndEnactmentDeadline({
maxEnactmentHours={maxEnactmentHours}
/>
)}
</>
</div>
);
}

View File

@ -109,6 +109,7 @@ describe('Propose Freeform', () => {
expect(screen.getByTestId('proposal-description')).toBeTruthy();
expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
expect(screen.getByTestId('proposal-download-json')).toBeTruthy();
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
});
});

View File

@ -12,18 +12,19 @@ import {
ProposalFormSubmit,
ProposalFormTitle,
ProposalFormTransactionDialog,
ProposalFormDownloadJson,
ProposalFormVoteAndEnactmentDeadline,
} from '../../components/propose';
import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import {
createDocsLinks,
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/react-helpers';
import { ProposalUserAction } from '../../components/shared';
import { downloadJson } from '../../../../lib/download-json';
export interface FreeformProposalFormFields {
proposalVoteDeadline: string;
@ -50,10 +51,11 @@ export const ProposeFreeform = () => {
handleSubmit,
formState: { isSubmitting, errors },
setValue,
watch,
} = useForm<FreeformProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: FreeformProposalFormFields) => {
const assembleProposal = (fields: FreeformProposalFormFields) => {
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
@ -66,7 +68,7 @@ export const ProposeFreeform = () => {
params.governance_proposal_freeform_maxClose
).toString();
await submit({
return {
rationale: {
title: fields.proposalTitle,
description: fields.proposalDescription,
@ -79,86 +81,101 @@ export const ProposeFreeform = () => {
isVoteDeadlineAtMaximum
),
},
});
};
};
const onSubmit = async (fields: FreeformProposalFormFields) => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
'vega-freeform-proposal'
);
};
return (
<AsyncRenderer loading={loading} error={error} data={params}>
<Heading title={t('NewFreeformProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_freeform_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
<AsyncRenderer
loading={loading}
error={error}
data={params}
render={(params) => (
<>
<Heading title={t('NewFreeformProposal')} />
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank"
>{`${
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_freeform_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreProposalsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/governance`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/governance`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreProposalsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/governance`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/governance`}</ExternalLink>
</p>
)}
<div data-testid="freeform-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<div data-testid="freeform-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_freeform_minClose}
voteMaxClose={params.governance_proposal_freeform_maxClose}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_freeform_minClose}
voteMaxClose={params.governance_proposal_freeform_maxClose}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};

View File

@ -112,6 +112,7 @@ describe('Propose Network Parameter', () => {
expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
expect(screen.getByTestId('proposal-download-json')).toBeTruthy();
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
});

View File

@ -20,6 +20,7 @@ import {
ProposalFormTitle,
ProposalFormTransactionDialog,
ProposalFormVoteAndEnactmentDeadline,
ProposalFormDownloadJson,
} from '../../components/propose';
import { ProposalMinRequirements } from '../../components/shared';
import {
@ -33,8 +34,8 @@ import {
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '../../components/shared';
import { downloadJson } from '../../../../lib/download-json';
interface SelectedNetworkParamCurrentValueProps {
value: string;
@ -92,6 +93,7 @@ export const ProposeNetworkParameter = () => {
handleSubmit,
formState: { isSubmitting, errors },
setValue,
watch,
} = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@ -99,8 +101,8 @@ export const ProposeNetworkParameter = () => {
? Object.entries(params).find(([key]) => key === selectedNetworkParam)
: null;
const onSubmit = async (fields: NetworkParameterProposalFormFields) => {
const acutalNetworkParamKey = fields.proposalNetworkParameterKey
const assembleProposal = (fields: NetworkParameterProposalFormFields) => {
const actualNetworkParamKey = fields.proposalNetworkParameterKey
.split('_')
.join('.');
@ -121,7 +123,7 @@ export const ProposeNetworkParameter = () => {
params.governance_proposal_updateNetParam_maxEnact
);
await submit({
return {
rationale: {
title: fields.proposalTitle,
description: fields.proposalDescription,
@ -129,7 +131,7 @@ export const ProposeNetworkParameter = () => {
terms: {
updateNetworkParameter: {
changes: {
key: acutalNetworkParamKey,
key: actualNetworkParamKey,
value: fields.proposalNetworkParameterValue,
},
},
@ -144,7 +146,19 @@ export const ProposeNetworkParameter = () => {
isEnactmentDeadlineAtMaximum
),
},
});
};
};
const onSubmit = async (fields: NetworkParameterProposalFormFields) => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
'vega-network-param-proposal'
);
};
return (
@ -152,163 +166,161 @@ export const ProposeNetworkParameter = () => {
loading={networkParamsLoading}
error={networkParamsError}
data={params}
>
<Heading title={t('NetworkParameterProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_updateNetParam_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
render={(params) => (
<>
<Heading title={t('NetworkParameterProposal')} />
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_updateNetParam_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank"
>{`${
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreNetParamsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/network-parameters`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/network-parameters`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreNetParamsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/network-parameters`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/network-parameters`}</ExternalLink>
</p>
)}
<div data-testid="network-parameter-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<div data-testid="network-parameter-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<ProposalFormTitle
registerField={register('proposalTitle', {
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>
{t('SelectAParameterToChange')}
</ProposalFormSubheader>
<FormGroup
label={t('SelectAParameterToChange')}
labelFor="proposal-parameter-key"
hideLabel={true}
>
<Select
data-testid="proposal-parameter-select"
id="proposal-parameter-key"
{...register('proposalNetworkParameterKey', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>
{t('SelectAParameterToChange')}
</ProposalFormSubheader>
<FormGroup
label={t('SelectAParameterToChange')}
labelFor="proposal-parameter-key"
hideLabel={true}
onChange={(e) => setSelectedNetworkParam(e.target.value)}
value={selectedNetworkParam}
>
<Select
data-testid="proposal-parameter-select"
id="proposal-parameter-key"
{...register('proposalNetworkParameterKey', {
required: t('Required'),
})}
onChange={(e) => setSelectedNetworkParam(e.target.value)}
value={selectedNetworkParam}
>
<option value="">{t('SelectParameter')}</option>
{Object.keys(params).map((key) => {
const actualKey = key.split('_').join('.');
return (
<option key={key} value={key}>
{actualKey}
</option>
);
})}
</Select>
{errors?.proposalNetworkParameterKey?.message && (
<InputError intent="danger">
{errors?.proposalNetworkParameterKey?.message}
</InputError>
)}
</FormGroup>
{selectedNetworkParam && (
<div className="mt-[-10px]">
{selectedParamEntry && (
<SelectedNetworkParamCurrentValue
value={selectedParamEntry[1]}
/>
)}
<FormGroup
label={t('NewProposedValue')}
labelFor="proposal-parameter-new-value"
>
<TextArea
data-testid="selected-proposal-param-new-value"
id="proposal-parameter-new-value"
{...register('proposalNetworkParameterValue', {
required: t('Required'),
})}
/>
{errors?.proposalNetworkParameterValue?.message && (
<InputError intent="danger">
{errors?.proposalNetworkParameterValue?.message}
</InputError>
)}
</FormGroup>
</div>
<option value="">{t('SelectParameter')}</option>
{Object.keys(params).map((key) => {
const actualKey = key.split('_').join('.');
return (
<option key={key} value={key}>
{actualKey}
</option>
);
})}
</Select>
{errors?.proposalNetworkParameterKey?.message && (
<InputError intent="danger">
{errors?.proposalNetworkParameterKey?.message}
</InputError>
)}
</FormGroup>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={
params.governance_proposal_updateNetParam_minClose
}
voteMaxClose={
params.governance_proposal_updateNetParam_maxClose
}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={
params.governance_proposal_updateNetParam_minEnact
}
enactmentMaxClose={
params.governance_proposal_updateNetParam_maxEnact
}
/>
{selectedNetworkParam && (
<div className="mt-[-10px]">
{selectedParamEntry && (
<SelectedNetworkParamCurrentValue
value={selectedParamEntry[1]}
/>
)}
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
<FormGroup
label={t('NewProposedValue')}
labelFor="proposal-parameter-new-value"
>
<TextArea
data-testid="selected-proposal-param-new-value"
id="proposal-parameter-new-value"
{...register('proposalNetworkParameterValue', {
required: t('Required'),
})}
/>
{errors?.proposalNetworkParameterValue?.message && (
<InputError intent="danger">
{errors?.proposalNetworkParameterValue?.message}
</InputError>
)}
</FormGroup>
</div>
)}
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={
params.governance_proposal_updateNetParam_minClose
}
voteMaxClose={
params.governance_proposal_updateNetParam_maxClose
}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={
params.governance_proposal_updateNetParam_minEnact
}
enactmentMaxClose={
params.governance_proposal_updateNetParam_maxEnact
}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};

View File

@ -110,6 +110,7 @@ describe('Propose New Asset', () => {
expect(screen.getByTestId('proposal-validation-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
expect(screen.getByTestId('proposal-download-json')).toBeTruthy();
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
});
});

View File

@ -21,13 +21,14 @@ import {
ProposalFormTerms,
ProposalFormTitle,
ProposalFormTransactionDialog,
ProposalFormDownloadJson,
ProposalFormVoteAndEnactmentDeadline,
} from '../../components/propose';
import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '../../components/shared';
import { downloadJson } from '../../../../lib/download-json';
export interface NewAssetProposalFormFields {
proposalVoteDeadline: string;
@ -62,10 +63,11 @@ export const ProposeNewAsset = () => {
handleSubmit,
formState: { isSubmitting, errors },
setValue,
watch,
} = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: NewAssetProposalFormFields) => {
const assembleProposal = (fields: NewAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
fields.proposalVoteDeadline,
params.governance_proposal_asset_minClose
@ -87,7 +89,7 @@ export const ProposeNewAsset = () => {
params.governance_proposal_asset_maxClose
);
await submit({
return {
rationale: {
title: fields.proposalTitle,
description: fields.proposalDescription,
@ -111,7 +113,19 @@ export const ProposeNewAsset = () => {
isValidationDeadlineAtMaximum
),
},
});
};
};
const onSubmit = async (fields: NewAssetProposalFormFields) => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
'vega-new-asset-proposal'
);
};
return (
@ -119,112 +133,111 @@ export const ProposeNewAsset = () => {
loading={networkParamsLoading}
error={networkParamsError}
data={params}
>
<Heading title={t('NewAssetProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_asset_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
render={(params) => (
<>
<Heading title={t('NewAssetProposal')} />
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank"
>{`${
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_asset_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreAssetsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/assets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/assets`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreAssetsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/assets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/assets`}</ExternalLink>
</p>
)}
<div data-testid="new-asset-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<div data-testid="new-asset-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>{t('NewAsset')}</ProposalFormSubheader>
<ProposalFormSubheader>{t('NewAsset')}</ProposalFormSubheader>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={'Terms.newAsset (JSON format)'}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={'Terms.newAsset (JSON format)'}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_asset_minClose}
voteMaxClose={params.governance_proposal_asset_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={params.governance_proposal_asset_minEnact}
enactmentMaxClose={params.governance_proposal_asset_maxEnact}
validationRequired={true}
onValidationMinMax={setValue}
validationRegister={register('proposalValidationDeadline', {
required: t('Required'),
})}
validationErrorMessage={
errors?.proposalValidationDeadline?.message
}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_asset_minClose}
voteMaxClose={params.governance_proposal_asset_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={params.governance_proposal_asset_minEnact}
enactmentMaxClose={params.governance_proposal_asset_maxEnact}
validationRequired={true}
onValidationMinMax={setValue}
validationRegister={register('proposalValidationDeadline', {
required: t('Required'),
})}
validationErrorMessage={
errors?.proposalValidationDeadline?.message
}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};

View File

@ -104,6 +104,7 @@ describe('Propose New Market', () => {
expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
expect(screen.getByTestId('proposal-download-json')).toBeTruthy();
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
});
});

View File

@ -20,13 +20,14 @@ import {
ProposalFormTerms,
ProposalFormTitle,
ProposalFormTransactionDialog,
ProposalFormDownloadJson,
ProposalFormVoteAndEnactmentDeadline,
} from '../../components/propose';
import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '../../components/shared';
import { downloadJson } from '../../../../lib/download-json';
export interface NewMarketProposalFormFields {
proposalVoteDeadline: string;
@ -60,10 +61,11 @@ export const ProposeNewMarket = () => {
handleSubmit,
formState: { isSubmitting, errors },
setValue,
watch,
} = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: NewMarketProposalFormFields) => {
const assembleProposal = (fields: NewMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
fields.proposalVoteDeadline,
params.governance_proposal_market_minClose
@ -81,7 +83,7 @@ export const ProposeNewMarket = () => {
params.governance_proposal_market_maxEnact
);
await submit({
return {
rationale: {
title: fields.proposalTitle,
description: fields.proposalDescription,
@ -101,7 +103,19 @@ export const ProposeNewMarket = () => {
isEnactmentDeadlineAtMaximum
),
},
});
};
};
const onSubmit = async (fields: NewMarketProposalFormFields) => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
'vega-new-market-proposal'
);
};
return (
@ -109,104 +123,103 @@ export const ProposeNewMarket = () => {
loading={networkParamsLoading}
error={networkParamsError}
data={params}
>
<Heading title={t('NewMarketProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_market_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
render={(params) => (
<>
<Heading title={t('NewMarketProposal')} />
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank"
>{`${
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_market_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreMarketsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/markets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/markets`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreMarketsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/markets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/markets`}</ExternalLink>
</p>
)}
<div data-testid="new-market-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<div data-testid="new-market-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>{t('NewMarket')}</ProposalFormSubheader>
<ProposalFormSubheader>{t('NewMarket')}</ProposalFormSubheader>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={'Terms.newMarket (JSON format)'}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={'Terms.newMarket (JSON format)'}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_market_minClose}
voteMaxClose={params.governance_proposal_market_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={params.governance_proposal_market_minEnact}
enactmentMaxClose={params.governance_proposal_market_maxEnact}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_market_minClose}
voteMaxClose={params.governance_proposal_market_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={params.governance_proposal_market_minEnact}
enactmentMaxClose={params.governance_proposal_market_maxEnact}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};

View File

@ -2,7 +2,6 @@ import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import {
AsyncRenderer,
ExternalLink,
@ -20,8 +19,10 @@ import { useProposalSubmit } from '@vegaprotocol/governance';
import {
ProposalFormSubmit,
ProposalFormTransactionDialog,
ProposalFormDownloadJson,
} from '../../components/propose';
import { ProposalRawMinRequirements } from './proposal-raw-min-requirements';
import { downloadJson } from '../../../../lib/download-json';
export interface RawProposalFormFields {
rawProposalData: string;
@ -48,6 +49,7 @@ export const ProposeRaw = () => {
register,
handleSubmit,
formState: { isSubmitting, errors },
watch,
} = useForm<RawProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@ -57,88 +59,90 @@ export const ProposeRaw = () => {
await submit(JSON.parse(fields.rawProposalData));
};
const viewJson = () => {
const formData = watch();
downloadJson(JSON.stringify(formData), 'vega-raw-proposal');
};
return (
<AsyncRenderer
loading={networkParamsLoading}
error={networkParamsError}
data={params}
>
<Heading title={t('NewRawProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalRawMinRequirements
assetMin={params.governance_proposal_asset_minProposerBalance}
updateAssetMin={
params.governance_proposal_updateAsset_minProposerBalance
}
marketMin={params.governance_proposal_market_minProposerBalance}
updateMarketMin={
params.governance_proposal_updateMarket_minProposerBalance
}
updateNetParamMin={
params.governance_proposal_updateNetParam_minProposerBalance
}
freeformMin={
params.governance_proposal_freeform_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
/>
render={(params) => (
<>
<Heading title={t('NewRawProposal')} />
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
</ExternalLink>
</p>
)}
<ProposalRawMinRequirements
assetMin={params.governance_proposal_asset_minProposerBalance}
updateAssetMin={
params.governance_proposal_updateAsset_minProposerBalance
}
marketMin={params.governance_proposal_market_minProposerBalance}
updateMarketMin={
params.governance_proposal_updateMarket_minProposerBalance
}
updateNetParamMin={
params.governance_proposal_updateNetParam_minProposerBalance
}
freeformMin={params.governance_proposal_freeform_minProposerBalance}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
/>
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreProposalsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/governance`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/governance`}</ExternalLink>
</p>
)}
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
</ExternalLink>
</p>
)}
<div data-testid="raw-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup
label="Make a proposal by submitting JSON"
labelFor="proposal-data"
>
<TextArea
id="proposal-data"
className="min-h-[200px]"
hasError={hasError}
data-testid="proposal-data"
{...register('rawProposalData', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
/>
{errors.rawProposalData?.message && (
<InputError intent="danger">
{errors.rawProposalData?.message}
</InputError>
)}
</FormGroup>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreProposalsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/governance`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/governance`}</ExternalLink>
</p>
)}
<div data-testid="raw-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup
label="Make a proposal by submitting JSON"
labelFor="proposal-data"
>
<TextArea
id="proposal-data"
className="min-h-[200px]"
hasError={hasError}
data-testid="proposal-data"
{...register('rawProposalData', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
{errors.rawProposalData?.message && (
<InputError intent="danger">
{errors.rawProposalData?.message}
</InputError>
)}
</FormGroup>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};

View File

@ -111,6 +111,7 @@ describe('Propose Update Asset', () => {
expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
expect(screen.getByTestId('proposal-download-json')).toBeTruthy();
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
});
});

View File

@ -20,13 +20,14 @@ import {
ProposalFormTerms,
ProposalFormTitle,
ProposalFormTransactionDialog,
ProposalFormDownloadJson,
ProposalFormVoteAndEnactmentDeadline,
} from '../../components/propose';
import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '../../components/shared';
import { downloadJson } from '../../../../lib/download-json';
export interface UpdateAssetProposalFormFields {
proposalVoteDeadline: string;
@ -60,10 +61,11 @@ export const ProposeUpdateAsset = () => {
handleSubmit,
formState: { isSubmitting, errors },
setValue,
watch,
} = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: UpdateAssetProposalFormFields) => {
const assembleProposal = (fields: UpdateAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
fields.proposalVoteDeadline,
params.governance_proposal_updateAsset_minClose
@ -81,7 +83,7 @@ export const ProposeUpdateAsset = () => {
params.governance_proposal_updateAsset_maxEnact
);
await submit({
return {
rationale: {
title: fields.proposalTitle,
description: fields.proposalDescription,
@ -101,7 +103,19 @@ export const ProposeUpdateAsset = () => {
isEnactmentDeadlineAtMaximum
),
},
});
};
};
const onSubmit = async (fields: UpdateAssetProposalFormFields) => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
'vega-update-asset-proposal'
);
};
return (
@ -109,110 +123,107 @@ export const ProposeUpdateAsset = () => {
loading={networkParamsLoading}
error={networkParamsError}
data={params}
>
<Heading title={t('UpdateAssetProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_updateAsset_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
render={(params) => (
<>
<Heading title={t('UpdateAssetProposal')} />
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank"
>{`${
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_updateAsset_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreAssetsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/assets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/assets`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreAssetsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/assets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/assets`}</ExternalLink>
</p>
)}
<div data-testid="update-asset-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<div data-testid="update-asset-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>
{t('UpdateAsset')}
</ProposalFormSubheader>
<ProposalFormSubheader>{t('UpdateAsset')}</ProposalFormSubheader>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={'Terms.updateAsset (JSON format)'}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={'Terms.updateAsset (JSON format)'}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_updateAsset_minClose}
voteMaxClose={params.governance_proposal_updateAsset_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={
params.governance_proposal_updateAsset_minEnact
}
enactmentMaxClose={
params.governance_proposal_updateAsset_maxEnact
}
/>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_updateAsset_minClose}
voteMaxClose={params.governance_proposal_updateAsset_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={
params.governance_proposal_updateAsset_minEnact
}
enactmentMaxClose={
params.governance_proposal_updateAsset_maxEnact
}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};

View File

@ -187,6 +187,7 @@ describe('Propose Update Market', () => {
expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy();
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
expect(screen.getByTestId('proposal-download-json')).toBeTruthy();
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
});

View File

@ -21,6 +21,7 @@ import {
ProposalFormTerms,
ProposalFormTitle,
ProposalFormTransactionDialog,
ProposalFormDownloadJson,
ProposalFormVoteAndEnactmentDeadline,
} from '../../components/propose';
import { ProposalMinRequirements } from '../../components/shared';
@ -34,9 +35,9 @@ import {
Select,
} from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
import { ProposalUserAction } from '../../components/shared';
import { useProposalMarketsQueryQuery } from './__generated___/UpdateMarket';
import { downloadJson } from '../../../../lib/download-json';
export interface UpdateMarketProposalFormFields {
proposalVoteDeadline: string;
@ -101,10 +102,11 @@ export const ProposeUpdateMarket = () => {
handleSubmit,
formState: { isSubmitting, errors },
setValue,
watch,
} = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: UpdateMarketProposalFormFields) => {
const assembleProposal = (fields: UpdateMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
fields.proposalVoteDeadline,
params.governance_proposal_updateMarket_minClose
@ -122,7 +124,7 @@ export const ProposeUpdateMarket = () => {
params.governance_proposal_updateMarket_maxEnact
);
await submit({
return {
rationale: {
title: fields.proposalTitle,
description: fields.proposalDescription,
@ -145,176 +147,182 @@ export const ProposeUpdateMarket = () => {
isEnactmentDeadlineAtMaximum
),
},
});
};
};
const onSubmit = async (fields: UpdateMarketProposalFormFields) => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
'vega-update-market-proposal'
);
};
return (
<AsyncRenderer
loading={networkParamsLoading && marketsLoading}
error={networkParamsError && marketsError}
data={params && marketsData}
>
<Heading title={t('UpdateMarketProposal')} />
<VegaWalletContainer>
{() => (
<>
<ProposalMinRequirements
minProposalBalance={
params.governance_proposal_updateMarket_minProposerBalance
}
spamProtectionMin={params.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
data={{ ...params, ...marketsData }}
render={(data) => (
<>
<Heading title={t('UpdateMarketProposal')} />
<ProposalMinRequirements
minProposalBalance={
data.governance_proposal_updateMarket_minProposerBalance
}
spamProtectionMin={data.spam_protection_proposal_min_tokens}
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank"
>{`${
{VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreMarketsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/markets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/markets`}</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreMarketsInfo')}{' '}
<ExternalLink
href={`${VEGA_EXPLORER_URL}/markets`}
target="_blank"
>{`${VEGA_EXPLORER_URL}/markets`}</ExternalLink>
</p>
)}
<div data-testid="update-market-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<div data-testid="update-market-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<ProposalFormSubheader>
{t('ProposalRationale')}
</ProposalFormSubheader>
<ProposalFormTitle
registerField={register('proposalTitle', {
<ProposalFormTitle
registerField={register('proposalTitle', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>
{t('SelectAMarketToChange')}
</ProposalFormSubheader>
<FormGroup
label={t('SelectAMarketToChange')}
labelFor="proposal-market"
hideLabel={true}
>
<Select
data-testid="proposal-market-select"
id="proposal-market"
{...register('proposalMarketId', {
required: t('Required'),
})}
errorMessage={errors?.proposalTitle?.message}
/>
<ProposalFormDescription
registerField={register('proposalDescription', {
required: t('Required'),
})}
errorMessage={errors?.proposalDescription?.message}
/>
<ProposalFormSubheader>
{t('SelectAMarketToChange')}
</ProposalFormSubheader>
<FormGroup
label={t('SelectAMarketToChange')}
labelFor="proposal-market"
hideLabel={true}
onChange={(e) => setSelectedMarket(e.target.value)}
>
<Select
data-testid="proposal-market-select"
id="proposal-market"
{...register('proposalMarketId', {
required: t('Required'),
})}
onChange={(e) => setSelectedMarket(e.target.value)}
>
<option value="">{t('SelectMarket')}</option>
{sortedMarkets.map((market) => (
<option value={market.id} key={market.id}>
{market.tradableInstrument.instrument.name}
</option>
))}
</Select>
{errors?.proposalMarketId?.message && (
<InputError intent="danger">
{errors?.proposalMarketId?.message}
</InputError>
)}
</FormGroup>
{selectedMarket && (
<div className="mt-[-20px] mb-6">
<KeyValueTable data-testid="update-market-details">
<KeyValueTableRow>
{t('MarketName')}
{
marketsData?.marketsConnection?.edges?.find(
({ node: market }) => market.id === selectedMarket
)?.node.tradableInstrument.instrument.name
}
</KeyValueTableRow>
<KeyValueTableRow>
{t('MarketCode')}
{
marketsData?.marketsConnection?.edges?.find(
({ node: market }) => market.id === selectedMarket
)?.node.tradableInstrument.instrument.code
}
</KeyValueTableRow>
<KeyValueTableRow>
{t('MarketId')}
{selectedMarket}
</KeyValueTableRow>
</KeyValueTable>
</div>
<option value="">{t('SelectMarket')}</option>
{sortedMarkets.map((market) => (
<option value={market.id} key={market.id}>
{market.tradableInstrument.instrument.name}
</option>
))}
</Select>
{errors?.proposalMarketId?.message && (
<InputError intent="danger">
{errors?.proposalMarketId?.message}
</InputError>
)}
</FormGroup>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={t('ProposeUpdateMarketTerms')}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
{selectedMarket && (
<div className="mt-[-20px] mb-6">
<KeyValueTable data-testid="update-market-details">
<KeyValueTableRow>
{t('MarketName')}
{
data.marketsConnection?.edges?.find(
({ node: market }) => market.id === selectedMarket
)?.node.tradableInstrument.instrument.name
}
</KeyValueTableRow>
<KeyValueTableRow>
{t('MarketCode')}
{
data.marketsConnection?.edges?.find(
({ node: market }) => market.id === selectedMarket
)?.node.tradableInstrument.instrument.code
}
</KeyValueTableRow>
<KeyValueTableRow>
{t('MarketId')}
{selectedMarket}
</KeyValueTableRow>
</KeyValueTable>
</div>
)}
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={
params.governance_proposal_updateMarket_minClose
}
voteMaxClose={
params.governance_proposal_updateMarket_maxClose
}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={
params.governance_proposal_updateMarket_minEnact
}
enactmentMaxClose={
params.governance_proposal_updateMarket_maxEnact
}
/>
<ProposalFormTerms
registerField={register('proposalTerms', {
required: t('Required'),
validate: (value) => validateJson(value),
})}
labelOverride={t('ProposeUpdateMarketTerms')}
errorMessage={errors?.proposalTerms?.message}
docsLink={DOCS_LINK}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
</VegaWalletContainer>
</AsyncRenderer>
<ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', {
required: t('Required'),
})}
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={data.governance_proposal_updateMarket_minClose}
voteMaxClose={data.governance_proposal_updateMarket_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
enactmentErrorMessage={
errors?.proposalEnactmentDeadline?.message
}
enactmentMinClose={
data.governance_proposal_updateMarket_minEnact
}
enactmentMaxClose={
data.governance_proposal_updateMarket_maxEnact
}
/>
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
/>
</form>
</div>
</>
)}
/>
);
};