Compare commits

...
14 changed files with 361 additions and 141 deletions
-4
View File
@@ -34,10 +34,6 @@ export const TOP_LEVEL_ROUTES = [
name: 'Rewards',
path: Routes.REWARDS,
},
{
name: 'Restricted',
path: Routes.RESTRICTED,
},
];
export const TOKEN_DROPDOWN_ROUTES = [
@@ -0,0 +1,23 @@
import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { useUpdateProposal } from '@vegaprotocol/proposals';
import * as Schema from '@vegaprotocol/types';
export const LocalAssetDetailsDialog = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
const { data: proposal } = useUpdateProposal({
id,
proposalType: Schema.ProposalType.TYPE_UPDATE_ASSET,
});
return (
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
open={isOpen}
onChange={setOpen}
proposalId={proposal?.id ?? undefined}
/>
);
};
@@ -0,0 +1 @@
export * from './asset-details-dialog';
+2 -11
View File
@@ -1,7 +1,3 @@
import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { VegaConnectDialog, ViewAsDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../lib/vega-connectors';
import {
@@ -10,9 +6,9 @@ import {
} from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { RiskMessage } from '../components/welcome-dialog';
import { LocalAssetDetailsDialog as AssetDetailsDialog } from '../components/asset-details-dialog';
const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
return (
<>
<VegaConnectDialog
@@ -20,12 +16,7 @@ const DialogsContainer = () => {
riskMessage={<RiskMessage />}
/>
<ViewAsDialog connector={Connectors.view} />
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
open={isOpen}
onChange={setOpen}
/>
<AssetDetailsDialog />
<WelcomeDialog />
<Web3ConnectUncontrolledDialog />
<WithdrawalApprovalDialogContainer />
+2 -2
View File
@@ -1,5 +1,5 @@
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
import { useUpdateNetworkParametersToasts } from '@vegaprotocol/proposals';
import { useProposalNotificationToasts } from '@vegaprotocol/proposals';
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
@@ -7,7 +7,7 @@ import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
import { Links } from '../lib/links';
export const ToastsManager = () => {
useUpdateNetworkParametersToasts();
useProposalNotificationToasts();
useVegaTransactionToasts();
useEthereumTransactionToasts();
useEthereumWithdrawApprovalsToasts();
+4 -2
View File
@@ -9,7 +9,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { create } from 'zustand';
import { AssetDetailsTable } from './asset-details-table';
import { AssetProposalNotification } from '@vegaprotocol/proposals';
import { AssetProposalNotification } from './asset-proposal-notification';
import { useAssetDataProvider } from './asset-data-provider';
export type AssetDetailsDialogStore = {
@@ -47,6 +47,7 @@ export interface AssetDetailsDialogProps {
open: boolean;
onChange: (open: boolean) => void;
asJson?: boolean;
proposalId?: string;
}
export const AssetDetailsDialog = ({
@@ -55,6 +56,7 @@ export const AssetDetailsDialog = ({
open,
onChange,
asJson = false,
proposalId,
}: AssetDetailsDialogProps) => {
const { data: asset } = useAssetDataProvider(assetId);
@@ -62,7 +64,7 @@ export const AssetDetailsDialog = ({
const content = asset ? (
<div className="my-2">
<AssetProposalNotification assetId={asset.id} />
<AssetProposalNotification proposalId={proposalId} />
{asJson ? (
<div className="pr-8">
<SyntaxHighlighter size="smaller" data={asset} />
@@ -1,24 +1,18 @@
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { useUpdateProposal } from '../lib';
type AssetProposalNotificationProps = {
assetId?: string;
proposalId?: string;
};
export const AssetProposalNotification = ({
assetId,
proposalId,
}: AssetProposalNotificationProps) => {
const tokenLink = useLinks(DApp.Governance);
const { data: proposal } = useUpdateProposal({
id: assetId,
proposalType: Schema.ProposalType.TYPE_UPDATE_ASSET,
});
if (proposal) {
if (proposalId) {
const proposalLink = tokenLink(
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
TOKEN_PROPOSAL.replace(':id', proposalId || '')
);
const message = (
<>
-1
View File
@@ -1,4 +1,3 @@
export * from './asset-proposal-notification';
export * from './market-proposal-notification';
export * from './proposals-list';
export * from './protocol-upgrade-countdown';
@@ -319,6 +319,7 @@ fragment NewAssetFields on NewAsset {
decimals
quantum
source {
__typename
... on BuiltinAsset {
maxFaucetAmountMint
}
@@ -488,3 +489,39 @@ subscription MarketViewLiveProposals {
...MarketViewProposalFields
}
}
fragment NotificationProposalsFields on Proposal {
id
state
terms {
closingDatetime
enactmentDatetime
change {
__typename
... on UpdateMarketState {
...UpdateMarketStateFields
}
... on NewMarket {
...NewMarketSuccessorFields
}
... on UpdateMarket {
...UpdateMarketFields
}
... on NewAsset {
...NewAssetFields
}
... on UpdateAsset {
...UpdateAssetFields
}
... on UpdateNetworkParameter {
...UpdateNetworkParameterFields
}
}
}
}
subscription NotificationProposals {
proposals {
...NotificationProposalsFields
}
}
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@ export * from './use-proposal-event';
export * from './use-vega-transaction';
export * from './use-proposal-submit';
export * from './use-update-proposal';
export * from './use-update-network-paramaters-toasts';
export * from './use-proposal-notification-toasts';
export * from './use-successor-market-proposal-details';
export * from './use-new-transfer-proposal-details';
export * from './use-cancel-transfer-proposal-details';
@@ -5,21 +5,21 @@ import { ProposalState } from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import {
PROPOSAL_STATES_TO_TOAST,
useUpdateNetworkParametersToasts,
} from './use-update-network-paramaters-toasts';
useProposalNotificationToasts,
} from './use-proposal-notification-toasts';
import type {
UpdateNetworkParameterProposalFragment,
OnUpdateNetworkParametersSubscription,
} from './__generated__/Proposal';
import { OnUpdateNetworkParametersDocument } from './__generated__/Proposal';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { waitFor, renderHook } from '@testing-library/react';
import { NotificationProposalsDocument } from '../proposals-data-provider/__generated__/Proposals';
const render = (mocks?: MockedResponse[]) => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MockedProvider mocks={mocks}>{children}</MockedProvider>
);
return renderHook(() => useUpdateNetworkParametersToasts(), { wrapper });
return renderHook(() => useProposalNotificationToasts(), { wrapper });
};
const generateUpdateNetworkParametersProposal = (
@@ -51,7 +51,7 @@ const clear = () => {
useToasts.setState(INITIAL);
};
describe('useUpdateNetworkParametersToasts', () => {
describe('useProposalNotificationToasts', () => {
beforeEach(clear);
afterAll(clear);
@@ -61,7 +61,7 @@ describe('useUpdateNetworkParametersToasts', () => {
const mockOpenProposal: MockedResponse<OnUpdateNetworkParametersSubscription> =
{
request: {
query: OnUpdateNetworkParametersDocument,
query: NotificationProposalsDocument,
},
result: {
data: {
@@ -89,7 +89,7 @@ describe('useUpdateNetworkParametersToasts', () => {
const mockFailedProposal: MockedResponse<OnUpdateNetworkParametersSubscription> =
{
request: {
query: OnUpdateNetworkParametersDocument,
query: NotificationProposalsDocument,
},
result: {
data: {
@@ -117,7 +117,7 @@ describe('useUpdateNetworkParametersToasts', () => {
const mockEmptyProposal: MockedResponse<OnUpdateNetworkParametersSubscription> =
{
request: {
query: OnUpdateNetworkParametersDocument,
query: NotificationProposalsDocument,
},
result: {
data: {
@@ -142,7 +142,7 @@ describe('useUpdateNetworkParametersToasts', () => {
{
terms: {
change: {
__typename: 'NewMarket',
__typename: 'NewSpotMarket',
},
},
}
@@ -150,7 +150,7 @@ describe('useUpdateNetworkParametersToasts', () => {
const mockWrongProposalType: MockedResponse<OnUpdateNetworkParametersSubscription> =
{
request: {
query: OnUpdateNetworkParametersDocument,
query: NotificationProposalsDocument,
},
result: {
data: {
@@ -0,0 +1,200 @@
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { ProposalStateMapping, MarketUpdateType } from '@vegaprotocol/types';
import { ProposalState } from '@vegaprotocol/types';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { useCallback } from 'react';
import type { NotificationProposalsFieldsFragment } from '../proposals-data-provider';
import { useNotificationProposalsSubscription } from '../proposals-data-provider';
export const PROPOSAL_STATES_TO_TOAST = [
ProposalState.STATE_DECLINED,
ProposalState.STATE_ENACTED,
ProposalState.STATE_OPEN,
ProposalState.STATE_PASSED,
];
const PROPOSAL_TYPES_TO_TOAST = [
'UpdateNetworkParameter',
'NewAsset',
'NewMarket',
'UpdateAsset',
'UpdateMarket',
'UpdateMarketState',
];
type Proposal = NotificationProposalsFieldsFragment;
const NotificationProposalToastContent = ({
proposal,
}: {
proposal: Proposal;
}) => {
const { data: assets } = useAssetsDataProvider();
const tokenLink = useLinks(DApp.Governance);
const change = proposal.terms.change;
let pretitle = 'New';
let content: ReactNode = null;
switch (change.__typename) {
case 'UpdateNetworkParameter':
pretitle = t('Network change');
content = (
<p className="italic">
'{t('Update ')}
<span className="break-all">{change.networkParameter.key}</span>
{t(' to ')}
<span>{change.networkParameter.value}</span>'
</p>
);
break;
case 'NewMarket':
pretitle = t('New market');
content = (
<p>
{t('New market ')}
<span className="break-all">{change.instrument.name}</span>
{change.successorConfiguration?.parentMarketId && (
<span>
{' '}
{t('successor of %', [
change.successorConfiguration.parentMarketId,
])}
</span>
)}
</p>
);
break;
case 'UpdateMarket':
pretitle = t('Update market');
content = (
<p>
{t('Market ')}
<span className="break-all">
{change.updateMarketConfiguration.instrument.code}
</span>
</p>
);
break;
case 'UpdateMarketState':
if (
change.updateType ===
MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE
) {
pretitle = t('Market termination');
} else if (
change.updateType === MarketUpdateType.MARKET_STATE_UPDATE_TYPE_SUSPEND
) {
pretitle = t('Market suspension');
} else {
pretitle = t('Market resume');
}
content = (
<p>
{t('Market ')}
<span className="break-all">
{change.market.tradableInstrument.instrument.name}
</span>
{change.price && (
<span> {t('Final price will be %s', [change.price])}</span>
)}
</p>
);
break;
case 'NewAsset':
pretitle = t('New asset');
content = (
<p>
<span className="break-all">
{change.name} ({change.symbol})
</span>
{', '}
{t('type %s', [
change.source.__typename === 'ERC20' ? 'ERC20' : 'Builtin asset',
])}
</p>
);
break;
case 'UpdateAsset':
pretitle = t('Update asset');
content = (
<p>
<span className="break-all">
{assets?.find((asset) => asset.id === change.assetId)?.name ??
change.assetId}
</span>
</p>
);
break;
default:
}
const title = t('%s proposal %s', [
pretitle,
ProposalStateMapping[proposal.state].toLowerCase(),
]);
const enactment = Date.parse(proposal.terms.enactmentDatetime);
return (
<div>
<ToastHeading>{title}</ToastHeading>
{content}
{!isNaN(enactment) && (
<p>
{t('Enactment date:')} {getDateTimeFormat().format(enactment)}
</p>
)}
<p>
<ExternalLink
href={tokenLink(TOKEN_PROPOSAL).replace(':id', proposal?.id || '')}
>
{t('View proposal details')}
</ExternalLink>
</p>
</div>
);
};
export const useProposalNotificationToasts = () => {
const setToast = useToasts((store) => store.setToast);
const remove = useToasts((store) => store.remove);
const hasToast = useToasts((store) => store.hasToast);
const fromProposal = useCallback(
(proposal: Proposal): Toast => {
const id = `proposal-notification-${proposal.id}`;
return {
id,
intent: Intent.Warning,
content: <NotificationProposalToastContent proposal={proposal} />,
onClose: () => {
remove(id);
},
};
},
[remove]
);
const displayToast = (toast: Toast) => {
if (!hasToast(toast.id)) {
setToast(toast);
}
};
return useNotificationProposalsSubscription({
onData: ({ data }) => {
// note proposals is poorly named, it is actually a single proposal
const proposal = data.data?.proposals;
if (!proposal) return;
if (!PROPOSAL_TYPES_TO_TOAST.includes(proposal.terms.change.__typename)) {
return;
}
// if one of the following states show a toast
if (PROPOSAL_STATES_TO_TOAST.includes(proposal.state)) {
displayToast(fromProposal(proposal));
}
},
});
};
@@ -1,96 +0,0 @@
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { UpdateNetworkParameter } from '@vegaprotocol/types';
import { ProposalStateMapping } from '@vegaprotocol/types';
import { ProposalState } from '@vegaprotocol/types';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
import { useCallback } from 'react';
import type { UpdateNetworkParameterProposalFragment } from './__generated__/Proposal';
import { useOnUpdateNetworkParametersSubscription } from './__generated__/Proposal';
export const PROPOSAL_STATES_TO_TOAST = [
ProposalState.STATE_DECLINED,
ProposalState.STATE_ENACTED,
ProposalState.STATE_OPEN,
ProposalState.STATE_PASSED,
];
const CLOSE_AFTER = 0;
type Proposal = UpdateNetworkParameterProposalFragment;
const UpdateNetworkParameterToastContent = ({
proposal,
}: {
proposal: Proposal;
}) => {
const tokenLink = useLinks(DApp.Governance);
const change = proposal.terms.change as UpdateNetworkParameter;
const title = t('Network change proposal %s').replace(
'%s',
ProposalStateMapping[proposal.state].toLowerCase()
);
const enactment = Date.parse(proposal.terms.enactmentDatetime);
return (
<div>
<ToastHeading>{title}</ToastHeading>
<p className="italic">
'{t('Update ')}
<span className="break-all">{change.networkParameter.key}</span>
{t(' to ')}
<span>{change.networkParameter.value}</span>'
</p>
{!isNaN(enactment) && (
<p>
{t('Enactment date:')} {getDateTimeFormat().format(enactment)}
</p>
)}
<p>
<ExternalLink
href={tokenLink(TOKEN_PROPOSAL).replace(':id', proposal?.id || '')}
>
{t('View proposal details')}
</ExternalLink>
</p>
</div>
);
};
export const useUpdateNetworkParametersToasts = () => {
const { setToast, remove } = useToasts((store) => ({
setToast: store.setToast,
remove: store.remove,
}));
const fromProposal = useCallback(
(proposal: Proposal): Toast => {
const id = `update-network-param-proposal-${proposal.id}`;
return {
id: `update-network-param-proposal-${proposal.id}`,
intent: Intent.Warning,
content: <UpdateNetworkParameterToastContent proposal={proposal} />,
onClose: () => {
remove(id);
},
closeAfter: CLOSE_AFTER,
};
},
[remove]
);
return useOnUpdateNetworkParametersSubscription({
onData: ({ data }) => {
// note proposals is poorly named, it is actually a single proposal
const proposal = data.data?.proposals;
if (!proposal) return;
if (proposal.terms.change.__typename !== 'UpdateNetworkParameter') return;
// if one of the following states show a toast
if (PROPOSAL_STATES_TO_TOAST.includes(proposal.state)) {
setToast(fromProposal(proposal));
}
},
});
};