diff --git a/apps/trading/client-pages/portfolio/deposits-container.tsx b/apps/trading/client-pages/portfolio/deposits-container.tsx index 54c067bc8..ac7e32875 100644 --- a/apps/trading/client-pages/portfolio/deposits-container.tsx +++ b/apps/trading/client-pages/portfolio/deposits-container.tsx @@ -5,7 +5,7 @@ import { t, useDataProvider } from '@vegaprotocol/react-helpers'; import { useVegaWallet } from '@vegaprotocol/wallet'; export const DepositsContainer = () => { - const { pubKey } = useVegaWallet(); + const { pubKey, isReadOnly } = useVegaWallet(); const { data, loading, error } = useDataProvider({ dataProvider: depositsProvider, variables: { partyId: pubKey || '' }, @@ -30,15 +30,17 @@ export const DepositsContainer = () => { /> -
- -
+ {!isReadOnly && ( +
+ +
+ )} ); }; diff --git a/apps/trading/client-pages/portfolio/withdrawals-container.tsx b/apps/trading/client-pages/portfolio/withdrawals-container.tsx index 3394c52d3..c3b5ae32b 100644 --- a/apps/trading/client-pages/portfolio/withdrawals-container.tsx +++ b/apps/trading/client-pages/portfolio/withdrawals-container.tsx @@ -9,7 +9,7 @@ import { t, useDataProvider } from '@vegaprotocol/react-helpers'; import { VegaWalletContainer } from '../../components/vega-wallet-container'; export const WithdrawalsContainer = () => { - const { pubKey } = useVegaWallet(); + const { pubKey, isReadOnly } = useVegaWallet(); const { data, loading, error } = useDataProvider({ dataProvider: withdrawalProvider, variables: { partyId: pubKey || '' }, @@ -36,15 +36,17 @@ export const WithdrawalsContainer = () => { /> -
- -
+ {!isReadOnly && ( +
+ +
+ )} ); diff --git a/apps/trading/components/accounts-container/accounts-container.tsx b/apps/trading/components/accounts-container/accounts-container.tsx index 42abd06aa..79bc4329c 100644 --- a/apps/trading/components/accounts-container/accounts-container.tsx +++ b/apps/trading/components/accounts-container/accounts-container.tsx @@ -9,7 +9,7 @@ import { AccountManager } from '@vegaprotocol/accounts'; import { useDepositDialog } from '@vegaprotocol/deposits'; export const AccountsContainer = () => { - const { pubKey } = useVegaWallet(); + const { pubKey, isReadOnly } = useVegaWallet(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const openWithdrawalDialog = useWithdrawalDialog((store) => store.open); const openDepositDialog = useDepositDialog((store) => store.open); @@ -37,13 +37,16 @@ export const AccountsContainer = () => { onClickAsset={onClickAsset} onClickWithdraw={openWithdrawalDialog} onClickDeposit={openDepositDialog} + isReadOnly={isReadOnly} /> -
- -
+ {!isReadOnly && ( +
+ +
+ )} ); }; diff --git a/libs/accounts/src/lib/accounts-manager.spec.tsx b/libs/accounts/src/lib/accounts-manager.spec.tsx index 241d72710..a99d9d59c 100644 --- a/libs/accounts/src/lib/accounts-manager.spec.tsx +++ b/libs/accounts/src/lib/accounts-manager.spec.tsx @@ -23,13 +23,23 @@ jest.mock('@vegaprotocol/react-helpers', () => ({ describe('AccountManager', () => { it('change partyId should reload data provider', async () => { const { rerender } = render( - + ); expect( (helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables.partyId ).toEqual('partyOne'); await act(() => { - rerender(); + rerender( + + ); }); expect( (helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables.partyId @@ -38,7 +48,13 @@ describe('AccountManager', () => { it('update method should return proper result', async () => { await act(() => { - render(); + render( + + ); }); await waitFor(() => { expect(screen.getByText('No accounts')).toBeInTheDocument(); diff --git a/libs/accounts/src/lib/accounts-manager.tsx b/libs/accounts/src/lib/accounts-manager.tsx index b331c9541..728f6ea1f 100644 --- a/libs/accounts/src/lib/accounts-manager.tsx +++ b/libs/accounts/src/lib/accounts-manager.tsx @@ -16,6 +16,7 @@ interface AccountManagerProps { onClickAsset: (assetId: string) => void; onClickWithdraw?: (assetId?: string) => void; onClickDeposit?: (assetId?: string) => void; + isReadOnly: boolean; } export const AccountManager = ({ @@ -23,6 +24,7 @@ export const AccountManager = ({ onClickWithdraw, onClickDeposit, partyId, + isReadOnly, }: AccountManagerProps) => { const gridRef = useRef(null); const dataRef = useRef(null); @@ -58,6 +60,7 @@ export const AccountManager = ({ onClickAsset={onClickAsset} onClickDeposit={onClickDeposit} onClickWithdraw={onClickWithdraw} + isReadOnly={isReadOnly} />
{ it('should render correct columns', async () => { await act(async () => { render( - null} /> + null} + isReadOnly={false} + /> ); }); const expectedHeaders = ['Asset', 'Total', 'Used', 'Available', '']; @@ -49,7 +53,11 @@ describe('AccountsTable', () => { it('should apply correct formatting', async () => { await act(async () => { render( - null} /> + null} + isReadOnly={false} + /> ); }); const cells = await screen.findAllByRole('gridcell'); diff --git a/libs/accounts/src/lib/accounts-table.tsx b/libs/accounts/src/lib/accounts-table.tsx index 91f53a484..e70bb36fa 100644 --- a/libs/accounts/src/lib/accounts-table.tsx +++ b/libs/accounts/src/lib/accounts-table.tsx @@ -29,6 +29,7 @@ export interface AccountTableProps extends AgGridReactProps { onClickAsset: (assetId: string) => void; onClickWithdraw?: (assetId: string) => void; onClickDeposit?: (assetId: string) => void; + isReadOnly: boolean; } export const AccountTable = forwardRef( @@ -127,48 +128,50 @@ export const AccountTable = forwardRef( } maxWidth={300} /> - ) => { - return data ? ( - <> - { - setOpenBreakdown(!openBreakdown); - setBreakdown(data.breakdown || null); - }} - > - {t('Breakdown')} - - - { - onClickDeposit && onClickDeposit(data.asset.id); - }} - > - {t('Deposit')} - - - - onClickWithdraw && onClickWithdraw(data.asset.id) - } - > - {t('Withdraw')} - - - ) : null; - }} - /> + {!props.isReadOnly && ( + ) => { + return data ? ( + <> + { + setOpenBreakdown(!openBreakdown); + setBreakdown(data.breakdown || null); + }} + > + {t('Breakdown')} + + + { + onClickDeposit && onClickDeposit(data.asset.id); + }} + > + {t('Deposit')} + + + + onClickWithdraw && onClickWithdraw(data.asset.id) + } + > + {t('Withdraw')} + + + ) : null; + }} + /> + )}
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index 0a71aa021..12bf52ef3 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -45,7 +45,7 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & { }; export const DealTicket = ({ market, submit }: DealTicketProps) => { - const { pubKey } = useVegaWallet(); + const { pubKey, isReadOnly } = useVegaWallet(); const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore( (store) => ({ getPersistedOrder: store.getOrder, @@ -158,7 +158,11 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => { ); return ( -
+ null : handleSubmit(onSubmit)} + className="p-4" + noValidate + > { /> )} = 1} + disabled={Object.keys(errors).length >= 1 || isReadOnly} variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'} /> @@ -241,9 +246,10 @@ interface SummaryMessageProps { errorMessage?: string; market: MarketDealTicket; order: OrderSubmissionBody['orderSubmission']; + isReadOnly: boolean; } const SummaryMessage = memo( - ({ errorMessage, market, order }: SummaryMessageProps) => { + ({ errorMessage, market, order, isReadOnly }: SummaryMessageProps) => { // Specific error UI for if balance is so we can // render a deposit dialog const asset = market.tradableInstrument.instrument.product.settlementAsset; @@ -251,6 +257,17 @@ const SummaryMessage = memo( market, order, }); + if (isReadOnly) { + return ( +
+ + { + 'You need to connect your own wallet to start trading on this market' + } + +
+ ); + } if (errorMessage === SummaryValidationType.NoCollateral) { return ( void; }) => { - const { pubKey } = useVegaWallet(); + const { pubKey, isReadOnly } = useVegaWallet(); if (!pubKey) { return {t('Please connect Vega wallet')}; @@ -21,6 +21,7 @@ export const OrderListContainer = ({ partyId={pubKey} marketId={marketId} onMarketClick={onMarketClick} + isReadOnly={isReadOnly} /> ); }; diff --git a/libs/orders/src/lib/components/order-list-manager/order-list-manager.spec.tsx b/libs/orders/src/lib/components/order-list-manager/order-list-manager.spec.tsx index 0cd186fbb..0654d0c77 100644 --- a/libs/orders/src/lib/components/order-list-manager/order-list-manager.spec.tsx +++ b/libs/orders/src/lib/components/order-list-manager/order-list-manager.spec.tsx @@ -13,7 +13,7 @@ const generateJsx = () => { return ( - + ); diff --git a/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx b/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx index 30dd6b19f..bdab8da54 100644 --- a/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx +++ b/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx @@ -31,6 +31,7 @@ export interface OrderListManagerProps { partyId: string; marketId?: string; onMarketClick?: (marketId: string) => void; + isReadOnly: boolean; } export const TransactionComplete = ({ @@ -72,6 +73,7 @@ export const OrderListManager = ({ partyId, marketId, onMarketClick, + isReadOnly, }: OrderListManagerProps) => { const gridRef = useRef(null); const scrolledToTop = useRef(true); @@ -146,6 +148,7 @@ export const OrderListManager = ({ }} setEditOrder={setEditOrder} onMarketClick={onMarketClick} + isReadOnly={isReadOnly} />
{ expect(mockCancel).toHaveBeenCalledWith(order); }); + it('does not allow cancelling and editing for permitted orders if read only', async () => { + const mockEdit = jest.fn(); + const mockCancel = jest.fn(); + const order = generateOrder({ + type: Schema.OrderType.TYPE_LIMIT, + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, + liquidityProvision: null, + peggedOrder: null, + }); + await act(async () => { + render( + generateJsx({ + rowData: [order], + setEditOrder: mockEdit, + cancel: mockCancel, + isReadOnly: true, + }) + ); + }); + const amendCell = getAmendCell(); + expect(amendCell.queryAllByRole('button')).toHaveLength(0); + }); + it('shows if an order is a liquidity provision order and does not show order actions', async () => { const order = generateOrder({ type: Schema.OrderType.TYPE_LIMIT, diff --git a/libs/orders/src/lib/components/order-list/order-list.stories.tsx b/libs/orders/src/lib/components/order-list/order-list.stories.tsx index 38545af52..df53dfcce 100644 --- a/libs/orders/src/lib/components/order-list/order-list.stories.tsx +++ b/libs/orders/src/lib/components/order-list/order-list.stories.tsx @@ -22,6 +22,7 @@ const Template: Story = (args) => { setEditOrder={() => { return; }} + isReadOnly={false} />
); @@ -48,6 +49,7 @@ const Template2: Story = (args) => { rowData={args.data} cancel={cancel} setEditOrder={setEditOrder} + isReadOnly={false} />
void; setEditOrder: (order: Order) => void; onMarketClick?: (marketId: string) => void; + isReadOnly: boolean; }; export const OrderListTable = forwardRef( @@ -248,7 +249,7 @@ export const OrderListTable = forwardRef( minWidth={100} type="rightAligned" cellRenderer={({ data, node }: VegaICellRendererParams) => { - return data && isOrderAmendable(data) ? ( + return data && isOrderAmendable(data) && !props.isReadOnly ? ( <> void; }) => { - const { pubKey } = useVegaWallet(); + const { pubKey, isReadOnly } = useVegaWallet(); if (!pubKey) { return ( @@ -17,5 +17,11 @@ export const PositionsContainer = ({ ); } - return ; + return ( + + ); }; diff --git a/libs/positions/src/lib/positions-manager.tsx b/libs/positions/src/lib/positions-manager.tsx index 85a7bf379..0cdc31d2d 100644 --- a/libs/positions/src/lib/positions-manager.tsx +++ b/libs/positions/src/lib/positions-manager.tsx @@ -9,16 +9,45 @@ import { t } from '@vegaprotocol/react-helpers'; interface PositionsManagerProps { partyId: string; onMarketClick?: (marketId: string) => void; + isReadOnly: boolean; } export const PositionsManager = ({ partyId, onMarketClick, + isReadOnly, }: PositionsManagerProps) => { const gridRef = useRef(null); const { data, error, loading, getRows } = usePositionsData(partyId, gridRef); const create = useVegaTransactionStore((store) => store.create); - + const onClose = ({ + marketId, + openVolume, + }: { + marketId: string; + openVolume: string; + }) => + create({ + batchMarketInstructions: { + cancellations: [ + { + marketId, + orderId: '', // omit order id to cancel all active orders + }, + ], + submissions: [ + { + marketId: marketId, + type: Schema.OrderType.TYPE_MARKET as const, + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK as const, + side: openVolume.startsWith('-') + ? Schema.Side.SIDE_BUY + : Schema.Side.SIDE_SELL, + size: openVolume.replace('-', ''), + }, + ], + }, + }); return (
- create({ - batchMarketInstructions: { - cancellations: [ - { - marketId, - orderId: '', // omit order id to cancel all active orders - }, - ], - submissions: [ - { - marketId: marketId, - type: Schema.OrderType.TYPE_MARKET as const, - timeInForce: Schema.OrderTimeInForce - .TIME_IN_FORCE_FOK as const, - side: openVolume.startsWith('-') - ? Schema.Side.SIDE_BUY - : Schema.Side.SIDE_SELL, - size: openVolume.replace('-', ''), - }, - ], - }, - }) - } + onClose={onClose} noRowsOverlayComponent={() => null} + isReadOnly={isReadOnly} />
{ await act(async () => { - const { baseElement } = render(); + const { baseElement } = render( + + ); expect(baseElement).toBeTruthy(); }); }); -it('Render correct columns', async () => { +it('render correct columns', async () => { await act(async () => { - render(); + render(); }); const headers = screen.getAllByRole('columnheader'); @@ -64,7 +66,7 @@ it('Render correct columns', async () => { it('renders market name', async () => { await act(async () => { - render(); + render(); }); expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy(); }); @@ -75,7 +77,7 @@ it('Does not fail if the market name does not match the split pattern', async () Object.assign({}, singleRow, { marketName: breakingMarketName }), ]; await act(async () => { - render(); + render(); }); expect(screen.getByText(breakingMarketName)).toBeTruthy(); @@ -84,7 +86,9 @@ it('Does not fail if the market name does not match the split pattern', async () it('add color and sign to amount, displays positive notional value', async () => { let result: RenderResult; await act(async () => { - result = render(); + result = render( + + ); }); let cells = screen.getAllByRole('gridcell'); @@ -94,7 +98,10 @@ it('add color and sign to amount, displays positive notional value', async () => expect(cells[1].textContent).toEqual('1,230.0'); await act(async () => { result.rerender( - + ); }); cells = screen.getAllByRole('gridcell'); @@ -107,7 +114,9 @@ it('add color and sign to amount, displays positive notional value', async () => it('displays mark price', async () => { let result: RenderResult; await act(async () => { - result = render(); + result = render( + + ); }); let cells = screen.getAllByRole('gridcell'); @@ -123,6 +132,7 @@ it('displays mark price', async () => { Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION, }, ]} + isReadOnly={false} /> ); }); @@ -134,14 +144,19 @@ it('displays mark price', async () => { it("displays properly entry, liquidation price and liquidation bar and it's intent", async () => { let result: RenderResult; await act(async () => { - result = render(); + result = render( + + ); }); let cells = screen.getAllByRole('gridcell'); const entryPrice = cells[5].firstElementChild?.firstElementChild?.textContent; expect(entryPrice).toEqual('13.3'); await act(async () => { result.rerender( - + ); }); cells = screen.getAllByRole('gridcell'); @@ -149,7 +164,7 @@ it("displays properly entry, liquidation price and liquidation bar and it's inte it('displays leverage', async () => { await act(async () => { - render(); + render(); }); const cells = screen.getAllByRole('gridcell'); expect(cells[7].textContent).toEqual('1.1'); @@ -157,7 +172,7 @@ it('displays leverage', async () => { it('displays allocated margin', async () => { await act(async () => { - render(); + render(); }); const cells = screen.getAllByRole('gridcell'); const cell = cells[8]; @@ -166,7 +181,7 @@ it('displays allocated margin', async () => { it('displays realised and unrealised PNL', async () => { await act(async () => { - render(); + render(); }); const cells = screen.getAllByRole('gridcell'); expect(cells[9].textContent).toEqual('1.23'); @@ -181,6 +196,7 @@ it('displays close button', async () => { onClose={() => { return; }} + isReadOnly={false} /> ); }); @@ -196,6 +212,7 @@ it('do not display close button if openVolume is zero', async () => { onClose={() => { return; }} + isReadOnly={false} /> ); }); diff --git a/libs/positions/src/lib/positions-table.tsx b/libs/positions/src/lib/positions-table.tsx index de3e9ecbb..862678633 100644 --- a/libs/positions/src/lib/positions-table.tsx +++ b/libs/positions/src/lib/positions-table.tsx @@ -33,6 +33,7 @@ interface Props extends TypedDataAgGrid { onClose?: (data: Position) => void; onMarketClick?: (id: string) => void; style?: CSSProperties; + isReadOnly: boolean; } export interface AmountCellProps { @@ -378,7 +379,7 @@ export const PositionsTable = forwardRef( return getDateTimeFormat().format(new Date(value)); }} /> - {onClose ? ( + {onClose && !props.isReadOnly ? ( ) =>