Compare commits

...
Author SHA1 Message Date
asiaznik 6f005d123f chore: error suggestion 2023-08-28 14:25:47 +02:00
asiaznik f93c595560 fix(proposals): parsing block duration value 2023-08-24 09:32:13 +02:00
Radosław Szpiech 0f3e5595ba chore(trading): add dist loadfile to console-test run (#4609) 2023-08-23 13:13:33 +00:00
Bartłomiej Głownia d3dbdd2bd5 feat(trading): view stop order history (#4586) 2023-08-23 13:41:56 +02:00
daro-majandJoe Tsang 0767139712 test(trading): update cypress to version 12.17.0 (#4575)
Co-authored-by: Joe Tsang <30622993+jtsang586@users.noreply.github.com>
2023-08-23 13:34:10 +02:00
daro-maj 250492a02c test(trading): tests for distinguish between product types (#4589) 2023-08-23 11:06:16 +02:00
Maciek 29f3374c61 fix(trading): fix hidden sidebars on load (#4583) 2023-08-23 07:23:36 +00:00
Ciaran McGhie ba7b574a07 chore(ui-toolkit,react-helpers,announcements): bump versions add react react-dom to peer deps (#4602) 2023-08-22 18:51:13 +01:00
Art afd8650657 fix(proposals): error policy guard for proposal data provider (#4573) 2023-08-22 17:58:01 +01:00
Bartłomiej GłowniaandMatthew Russell 78414b4429 feat(ui-toolkit): form element design changes (#4525)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-08-22 17:39:52 +01:00
m.ray e0a91b3850 chore(trading): revert metadata update - viewport meta tags should not be used in _document.js's (#4591) 2023-08-22 17:23:24 +01:00
Joe Tsang 80f7a08765 test(governance): vote error test (#4588) 2023-08-22 16:15:33 +01:00
Edd 3e627ff849 fix(explorer): check linking type when summing stake (#4585) 2023-08-22 13:08:00 +00:00
Joe Tsang d2854b6e90 chore(governance): add acs for network nodes (#4577) 2023-08-22 12:06:41 +01:00
Matthew Russell 6d130c9cfc feat(positions): filter closed markets in positions table (#4569) 2023-08-22 10:17:10 +01:00
89 changed files with 1864 additions and 441 deletions
+1 -1
View File
@@ -125,7 +125,7 @@ jobs:
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -s --numprocesses auto
run: poetry run pytest -s --numprocesses auto --dist loadfile
- name: Check files
run: |
ls -al .
@@ -29,37 +29,6 @@ fragment ExplorerPartyAssetsAccounts on AccountBalance {
}
}
fragment ExplorerPartyLinks on AccountBalance {
asset {
name
id
decimals
symbol
source {
__typename
... on ERC20 {
contractAddress
}
}
}
type
balance
market {
id
decimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
}
}
query ExplorerPartyAssets($partyId: ID!) {
partiesConnection(id: $partyId) {
edges {
@@ -64,7 +64,7 @@ export const ExplorerPartyAssetsDocument = gql`
}
stakingSummary {
currentStakeAvailable
linkings(pagination: {first: 100}) {
linkings(pagination: {last: 100}) {
edges {
node {
type
@@ -113,4 +113,4 @@ export function useExplorerPartyAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHo
}
export type ExplorerPartyAssetsQueryHookResult = ReturnType<typeof useExplorerPartyAssetsQuery>;
export type ExplorerPartyAssetsLazyQueryHookResult = ReturnType<typeof useExplorerPartyAssetsLazyQuery>;
export type ExplorerPartyAssetsQueryResult = Apollo.QueryResult<ExplorerPartyAssetsQuery, ExplorerPartyAssetsQueryVariables>;
export type ExplorerPartyAssetsQueryResult = Apollo.QueryResult<ExplorerPartyAssetsQuery, ExplorerPartyAssetsQueryVariables>;
@@ -42,14 +42,14 @@ export const PartyBlockStake = ({
linkedLength && linkedLength > 0
? p?.stakingSummary?.linkings?.edges
?.reduce((total, e) => {
const accumulator = new BigNumber(total)
const diff = new BigNumber(e?.node.amount || 0)
const accumulator = new BigNumber(total);
const diff = new BigNumber(e?.node.amount || 0);
if (e?.node.type === 'TYPE_LINK') {
return accumulator.plus(diff);
} else if (e?.node.type === 'TYPE_UNLINK') {
return accumulator.minus(diff);
} else {
return accumulator
return accumulator;
}
}, new BigNumber(0))
.toString()
@@ -361,6 +361,34 @@ describe(
stakingPageDisassociateAllTokens();
});
it('Error message should be displayed if error returned from wallet when voting', function () {
const errorMsg =
'Application error: party has already submitted the maximum number of transactions of this type per epoch (3)';
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
);
cy.intercept('POST', '/api/v2/requests', {
jsonrpc: '2.0',
error: {
code: 2001,
message: 'Application error',
data: 'party has already submitted the maximum number of transactions of this type per epoch (3)',
},
id: '-PK5EGmErnjLhAmzMeclC',
});
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
cy.getByTestId('vote-buttons').contains('for').click();
cy.getByTestId('dialog-title').should(
'have.text',
'Transaction failed'
);
cy.getByTestId('Error').should('have.text', errorMsg);
});
});
it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
@@ -119,6 +119,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
// 0006-NETW-001 0006-NETW-002
it('should display network data', function () {
cy.getByTestId('git-network-data')
.should('contain.text', 'Reading network data from')
@@ -130,6 +131,37 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
// 0006-NETW-003 0006-NETW-008 0006-NETW-009 0006-NETW-010 0006-NETW-012 0006-NETW-013 0006-NETW-017 0006-NETW-018 0006-NETW-019 0006-NETW-020
it('should have option to switch to different network node', function () {
cy.getByTestId('git-network-data').within(() => {
cy.getByTestId('link').click();
});
cy.getByTestId('node-row').within(() => {
cy.getByTestId('node-url-0')
.parent()
.should('have.text', 'http://localhost:3008/graphql');
cy.getByTestId('response-time-cell')
.invoke('text')
.should('not.be.empty')
.and('not.eq', 'Checking');
cy.getByTestId('block-height-cell')
.invoke('text')
.should('not.be.empty')
.then((currentBlockHeight) => {
// Check that block height updates automatically
cy.getByTestId('block-height-cell')
.invoke('text')
.should('not.eq', currentBlockHeight);
});
cy.getByTestId('subscription-cell').should('have.text', 'Yes');
});
cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click();
cy.get('input').should('exist');
cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('icon-cross').click();
});
it('should display eth data', function () {
cy.getByTestId('git-eth-data')
.should('contain.text', 'Reading Ethereum data from')
@@ -138,6 +170,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
// 0006-NETW-011
it('should contain link for known issues on Github', function () {
cy.getByTestId('git-info').within(() => {
cy.contains('Known issues and feedback on')
@@ -183,9 +183,8 @@ export function clickOnValidatorFromList(
cy.get(`[row-id="${validatorNumber}"]`)
.should('be.visible')
.first()
.within(() => {
cy.get(stakeValidatorListName).click();
});
.as('validatorOnList');
cy.get('@validatorOnList').click();
}
}
@@ -259,6 +259,12 @@ describe('Closed markets', { tags: '@smoke' }, () => {
.find('[data-testid="market-code"]')
.should('have.text', settledMarket.tradableInstrument.instrument.code);
// 6001-MARK-071
cy.get(rowSelector)
.first()
.find('[title="Future"]')
.should('have.text', 'Futr');
// 6001-MARK-002
cy.get(rowSelector)
.first()
@@ -69,6 +69,12 @@ describe('markets all table', { tags: '@smoke' }, () => {
.find(colInstrumentCode)
.should('have.text', 'SOLUSD');
// 6001-MARK-073
cy.get(rowSelector)
.first()
.find('[title="Future"]')
.should('have.text', 'Futr');
// 6001-MARK-036
cy.get(rowSelector)
.first()
@@ -45,6 +45,12 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
.find('[col-id="description"]')
.should('have.text', 'ETHUSD');
// 6001-MARK-074
cy.get(rowSelector)
.first()
.find('[title="Future"]')
.should('have.text', 'Futr');
// 6001-MARK-051
cy.get(rowSelector)
.first()
@@ -54,6 +54,15 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
cy.getByTestId(toggleLimit).next('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
it('sidebar should be open after reload', () => {
cy.mockTradingPage();
cy.getByTestId('deal-ticket-form').should('be.visible');
cy.getByTestId('Order').click();
cy.getByTestId('deal-ticket-form').should('not.exist');
cy.reload();
cy.getByTestId('deal-ticket-form').should('be.visible');
});
});
describe(
@@ -222,12 +222,17 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
it('must see a filled order', () => {
// 7002-SORD-046
// 7003-MORD-020
// NOT COVERED: Must be able to see/link to all trades that were created from this order
updateOrder({
id: orderId,
status: Schema.OrderStatus.STATUS_FILLED,
});
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
cy.get('[col-id="market.tradableInstrument.instrument.code"]').contains(
'[title="Future"]',
'Futr'
);
});
it('must see a rejected order', () => {
@@ -94,7 +94,11 @@ const MainGrid = memo(
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<Tab
id="positions"
name={t('Positions')}
menu={<TradingViews.positions.menu />}
>
<TradingViews.positions.component />
</Tab>
<Tab
@@ -17,6 +17,7 @@ import type { OrderContainerProps } from '../../components/orders-container';
import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu';
import { PositionsMenu } from '../../components/positions-menu';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -57,7 +58,11 @@ export const TradingViews = {
label: 'Trades',
component: requiresMarket(TradesContainer),
},
positions: { label: 'Positions', component: PositionsContainer },
positions: {
label: 'Positions',
component: PositionsContainer,
menu: PositionsMenu,
},
activeOrders: {
label: 'Active',
component: (props: OrderContainerProps) => (
@@ -2,7 +2,7 @@ import { t } from '@vegaprotocol/i18n';
import uniqBy from 'lodash/uniqBy';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
import {
Input,
TradingInput,
TinyScroll,
VegaIcon,
VegaIconNames,
@@ -57,7 +57,7 @@ export const MarketSelector = ({
/>
<div className="text-sm grid grid-cols-[2fr_1fr_1fr] gap-1 ">
<div className="flex-1">
<Input
<TradingInput
onChange={(e) =>
setFilter((curr) => ({ ...curr, searchTerm: e.target.value }))
}
@@ -5,6 +5,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import type { StateCreator } from 'zustand';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
@@ -13,6 +14,7 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
const gridStore = usePositionsStore((store) => store.gridStore);
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore);
@@ -40,12 +42,35 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks}
showClosed={showClosed}
/>
);
};
const usePositionsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_positions_store',
})
type PositionsStoreSlice = {
showClosedMarkets: boolean;
toggleClosedMarkets: () => void;
};
const createPositionStoreSlice: StateCreator<PositionsStoreSlice> = (set) => ({
showClosedMarkets: false,
toggleClosedMarkets: () => {
set((curr) => {
return {
showClosedMarkets: !curr.showClosedMarkets,
};
});
},
});
export const usePositionsStore = create<PositionsStoreSlice & DataGridSlice>()(
persist(
(...args) => ({
...createPositionStoreSlice(...args),
...createDataGridSlice(...args),
}),
{
name: 'vega_positions_store',
}
)
);
@@ -0,0 +1 @@
export * from './positions-menu';
@@ -0,0 +1,18 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { usePositionsStore } from '../positions-container';
export const PositionsMenu = () => {
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={toggle}
>
{showClosed ? t('Hide closed markets') : t('Show closed markets')}
</TradingButton>
);
};
+10 -21
View File
@@ -14,12 +14,9 @@ import { Settings } from '../settings';
import { Tooltip } from '../../components/tooltip';
import { WithdrawContainer } from '../withdraw-container';
import { Routes as AppRoutes } from '../../pages/client-router';
import { persist } from 'zustand/middleware';
import { GetStarted } from '../welcome-dialog';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
const STORAGE_KEY = 'vega_sidebar_store';
export enum ViewType {
Order = 'Order',
Info = 'Info',
@@ -302,22 +299,14 @@ export const useSidebar = create<{
init: boolean;
view: SidebarView | null;
setView: (view: SidebarView | null) => void;
}>()(
persist(
(set) => ({
init: true,
view: null,
setView: (x) =>
set(() => {
if (x == null) {
return { view: null, init: false };
}
return { view: x, init: false };
}),
}>()((set) => ({
init: true,
view: null,
setView: (x) =>
set(() => {
if (x == null) {
return { view: null, init: false };
}
return { view: x, init: false };
}),
{
name: STORAGE_KEY,
}
)
);
}));
@@ -1,4 +1,4 @@
import { Checkbox } from '@vegaprotocol/ui-toolkit';
import { TradingCheckbox } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
@@ -7,7 +7,7 @@ export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
return (
<div className="flex flex-col py-3">
<div className="mr-4" role="form">
<Checkbox
<TradingCheckbox
label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
checked={isApproved}
name="telemetry-approval"
+9 -39
View File
@@ -1,40 +1,10 @@
import { Html, Head, Main, NextScript } from 'next/document';
import { Head, Html, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<>
<Head>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Console" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://console.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Console" />
<meta name="og:site_name" content="Vega Protocol - Console" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:title" content="Vega Protocol - Console" />
<meta name="twitter:description" content="Vega Protocol - Console" />
<meta
name="twitter:image"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Console" />
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
@@ -45,8 +15,6 @@ export default function Document() {
as="font"
type="font/woff2"
/>
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
<link
rel="icon"
type="image/x-icon"
@@ -54,10 +22,12 @@ export default function Document() {
/>
<script src="/theme-setter.js" type="text/javascript" async />
</Head>
<body className="bg-white dark:bg-vega-cdark-900 text-default font-alpha">
<Main />
<NextScript />
</body>
</Html>
<Html>
<body className="bg-white dark:bg-vega-cdark-900 text-default font-alpha">
<Main />
<NextScript />
</body>
</Html>
</>
);
}
+57 -1
View File
@@ -1,3 +1,4 @@
import Head from 'next/head';
import { ClientRouter } from './client-router';
/**
@@ -6,5 +7,60 @@ import { ClientRouter } from './client-router';
* have to serve a static site via next export
*/
export default function Index() {
return <ClientRouter />;
return (
<>
<Head>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Console" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://console.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Console" />
<meta name="og:site_name" content="Vega Protocol - Console" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:title" content="Vega Protocol - Console" />
<meta name="twitter:description" content="Vega Protocol - Console" />
<meta
name="twitter:image"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Console" />
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
/>
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<script src="/theme-setter.js" type="text/javascript" async />
</Head>
<ClientRouter />
</>
);
}
+31 -23
View File
@@ -9,13 +9,13 @@ import {
import { t } from '@vegaprotocol/i18n';
import {
Button,
FormGroup,
Input,
InputError,
RichSelect,
Select,
TradingFormGroup,
TradingInput,
TradingInputError,
TradingRichSelect,
TradingSelect,
Tooltip,
Checkbox,
TradingCheckbox,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
@@ -130,12 +130,16 @@ export const TransferForm = ({
className="text-sm"
data-testid="transfer-form"
>
<FormGroup label="Vega key" labelFor="to-address">
<TradingFormGroup label="Vega key" labelFor="to-address">
<AddressField
pubKeys={pubKeys}
onChange={() => setValue('toAddress', '')}
select={
<Select {...register('toAddress')} id="to-address" defaultValue="">
<TradingSelect
{...register('toAddress')}
id="to-address"
defaultValue=""
>
<option value="" disabled={true}>
{t('Please select')}
</option>
@@ -147,10 +151,10 @@ export const TransferForm = ({
{pk}
</option>
))}
</Select>
</TradingSelect>
}
input={
<Input
<TradingInput
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown
id="to-address"
@@ -171,12 +175,12 @@ export const TransferForm = ({
}
/>
{errors.toAddress?.message && (
<InputError forInput="to-address">
<TradingInputError forInput="to-address">
{errors.toAddress.message}
</InputError>
</TradingInputError>
)}
</FormGroup>
<FormGroup label="Asset" labelFor="asset">
</TradingFormGroup>
<TradingFormGroup label="Asset" labelFor="asset">
<Controller
control={control}
name="asset"
@@ -186,7 +190,7 @@ export const TransferForm = ({
},
}}
render={({ field }) => (
<RichSelect
<TradingRichSelect
data-testid="select-asset"
id={field.name}
name={field.name}
@@ -208,15 +212,17 @@ export const TransferForm = ({
}
/>
))}
</RichSelect>
</TradingRichSelect>
)}
/>
{errors.asset?.message && (
<InputError forInput="asset">{errors.asset.message}</InputError>
<TradingInputError forInput="asset">
{errors.asset.message}
</TradingInputError>
)}
</FormGroup>
<FormGroup label="Amount" labelFor="amount">
<Input
</TradingFormGroup>
<TradingFormGroup label="Amount" labelFor="amount">
<TradingInput
id="amount"
autoComplete="off"
appendElement={
@@ -239,11 +245,13 @@ export const TransferForm = ({
})}
/>
{errors.amount?.message && (
<InputError forInput="amount">{errors.amount.message}</InputError>
<TradingInputError forInput="amount">
{errors.amount.message}
</TradingInputError>
)}
</FormGroup>
</TradingFormGroup>
<div className="mb-4">
<Checkbox
<TradingCheckbox
name="include-transfer-fee"
disabled={!transferAmount}
label={
+5 -1
View File
@@ -1,4 +1,8 @@
{
"name": "@vegaprotocol/announcements",
"version": "0.0.2"
"version": "0.0.2",
"peerDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}
+3 -3
View File
@@ -1,4 +1,4 @@
import { Option } from '@vegaprotocol/ui-toolkit';
import { TradingOption } from '@vegaprotocol/ui-toolkit';
import type { AssetFieldsFragment } from './__generated__/Asset';
import classNames from 'classnames';
import { t } from '@vegaprotocol/i18n';
@@ -28,7 +28,7 @@ export const Balance = ({
export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
return (
<Option key={asset.id} value={asset.id}>
<TradingOption key={asset.id} value={asset.id}>
<div className="flex flex-col items-start">
<div className="flex flex-row align-baseline gap-2">
<span>{asset.name}</span>{' '}
@@ -49,6 +49,6 @@ export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
</span>
</div>
</div>
</Option>
</TradingOption>
);
};
@@ -15,7 +15,7 @@ import {
} from 'date-fns';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { InputError } from '@vegaprotocol/ui-toolkit';
import { TradingInputError } from '@vegaprotocol/ui-toolkit';
const defaultValue: Schema.DateRange = {};
export interface DateRangeFilterProps extends IFilterParams {
@@ -195,7 +195,7 @@ export const DateRangeFilter = forwardRef(
}, [value, props]);
const notification = useMemo(() => {
const not = error ? <InputError>{error}</InputError> : null;
const not = error ? <TradingInputError>{error}</TradingInputError> : null;
return (
<div className="ag-filter-apply-panel flex min-h-[2rem]">{not}</div>
);
@@ -1,4 +1,8 @@
import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import {
TradingFormGroup,
TradingInput,
TradingInputError,
} from '@vegaprotocol/ui-toolkit';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { DealTicketAmountProps } from './deal-ticket-amount';
@@ -22,17 +26,17 @@ export const DealTicketLimitAmount = ({
const renderError = () => {
if (sizeError) {
return (
<InputError testId="deal-ticket-error-message-size-limit">
<TradingInputError testId="deal-ticket-error-message-size-limit">
{sizeError}
</InputError>
</TradingInputError>
);
}
if (priceError) {
return (
<InputError testId="deal-ticket-error-message-price-limit">
<TradingInputError testId="deal-ticket-error-message-price-limit">
{priceError}
</InputError>
</TradingInputError>
);
}
@@ -43,7 +47,7 @@ export const DealTicketLimitAmount = ({
<div className="mb-2">
<div className="flex items-start gap-4">
<div className="flex-1">
<FormGroup
<TradingFormGroup
label={t('Size')}
labelFor="input-order-size-limit"
className="!mb-0"
@@ -59,8 +63,8 @@ export const DealTicketLimitAmount = ({
},
validate: validateAmount(sizeStep, 'Size'),
}}
render={({ field }) => (
<Input
render={({ field, fieldState }) => (
<TradingInput
id="input-order-size-limit"
className="w-full"
type="number"
@@ -68,15 +72,16 @@ export const DealTicketLimitAmount = ({
min={sizeStep}
data-testid="order-size"
onWheel={(e) => e.currentTarget.blur()}
hasError={!!fieldState.error}
{...field}
/>
)}
/>
</FormGroup>
</TradingFormGroup>
</div>
<div className="pt-7 leading-10">@</div>
<div className="pt-5 leading-10">@</div>
<div className="flex-1">
<FormGroup
<TradingFormGroup
labelFor="input-price-quote"
label={t(`Price (${quoteName})`)}
labelAlign="right"
@@ -93,19 +98,20 @@ export const DealTicketLimitAmount = ({
},
validate: validateAmount(priceStep, 'Price'),
}}
render={({ field }) => (
<Input
render={({ field, fieldState }) => (
<TradingInput
id="input-price-quote"
className="w-full"
type="number"
step={priceStep}
data-testid="order-price"
onWheel={(e) => e.currentTarget.blur()}
hasError={!!fieldState.error}
{...field}
/>
)}
/>
</FormGroup>
</TradingFormGroup>
</div>
</div>
{renderError()}
@@ -4,7 +4,11 @@ import {
validateAmount,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
import {
TradingInput,
TradingInputError,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { isMarketInAuction } from '@vegaprotocol/markets';
import type { DealTicketAmountProps } from './deal-ticket-amount';
import { Controller } from 'react-hook-form';
@@ -33,7 +37,7 @@ export const DealTicketMarketAmount = ({
<div className="mb-2">
<div className="flex items-start gap-4">
<div className="flex-1">
<div className="mb-2 text-sm">{t('Size')}</div>
<div className="mb-2 text-xs">{t('Size')}</div>
<Controller
name="size"
control={control}
@@ -45,8 +49,8 @@ export const DealTicketMarketAmount = ({
},
validate: validateAmount(sizeStep, 'Size'),
}}
render={({ field }) => (
<Input
render={({ field, fieldState }) => (
<TradingInput
id="input-order-size-market"
className="w-full"
type="number"
@@ -54,12 +58,13 @@ export const DealTicketMarketAmount = ({
min={sizeStep}
onWheel={(e) => e.currentTarget.blur()}
data-testid="order-size"
hasError={!!fieldState.error}
{...field}
/>
)}
/>
</div>
<div className="pt-7 leading-10">@</div>
<div className="pt-5 leading-10">@</div>
<div className="flex-1 text-sm text-right">
{inAuction && (
<Tooltip
@@ -72,7 +77,7 @@ export const DealTicketMarketAmount = ({
)}
<div
data-testid="last-price"
className={classNames('leading-10', { 'pt-7': !inAuction })}
className={classNames('leading-10', { 'pt-5': !inAuction })}
>
{priceFormatted && quoteName ? (
<>
@@ -85,12 +90,12 @@ export const DealTicketMarketAmount = ({
</div>
</div>
{sizeError && (
<InputError
<TradingInputError
intent="danger"
testId="deal-ticket-error-message-size-market"
>
{sizeError}
</InputError>
</TradingInputError>
)}
</div>
);
@@ -4,9 +4,9 @@ import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
FormGroup,
Input,
InputError,
TradingFormGroup,
TradingInput,
TradingInputError,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
@@ -32,9 +32,9 @@ export const DealTicketSizeIceberg = ({
const renderPeakSizeError = () => {
if (peakSizeError) {
return (
<InputError testId="deal-ticket-peak-error-message-size-limit">
<TradingInputError testId="deal-ticket-peak-error-message-size-limit">
{peakSizeError}
</InputError>
</TradingInputError>
);
}
@@ -44,9 +44,9 @@ export const DealTicketSizeIceberg = ({
const renderMinimumSizeError = () => {
if (minimumVisibleSizeError) {
return (
<InputError testId="deal-ticket-minimum-error-message-size-limit">
<TradingInputError testId="deal-ticket-minimum-error-message-size-limit">
{minimumVisibleSizeError}
</InputError>
</TradingInputError>
);
}
@@ -57,7 +57,7 @@ export const DealTicketSizeIceberg = ({
<div className="mb-2">
<div className="flex items-center gap-4">
<div className="flex-1">
<FormGroup
<TradingFormGroup
label={
<Tooltip
description={
@@ -93,7 +93,7 @@ export const DealTicketSizeIceberg = ({
validate: validateAmount(sizeStep, 'peakSize'),
}}
render={({ field }) => (
<Input
<TradingInput
id="input-order-peak-size"
className="w-full"
type="number"
@@ -106,14 +106,14 @@ export const DealTicketSizeIceberg = ({
/>
)}
/>
</FormGroup>
</TradingFormGroup>
</div>
<div className="flex-0 items-center">
<div className="flex"></div>
<div className="flex"></div>
</div>
<div className="flex-1">
<FormGroup
<TradingFormGroup
label={
<Tooltip
description={
@@ -151,7 +151,7 @@ export const DealTicketSizeIceberg = ({
validate: validateAmount(sizeStep, 'minimumVisibleSize'),
}}
render={({ field }) => (
<Input
<TradingInput
id="input-order-minimum-size"
className="w-full"
type="number"
@@ -164,7 +164,7 @@ export const DealTicketSizeIceberg = ({
/>
)}
/>
</FormGroup>
</TradingFormGroup>
</div>
</div>
{renderPeakSizeError()}
@@ -10,13 +10,13 @@ import {
import { useForm, Controller, useController } from 'react-hook-form';
import * as Schema from '@vegaprotocol/types';
import {
Radio,
RadioGroup,
Input,
Checkbox,
FormGroup,
InputError,
Select,
TradingRadio,
TradingRadioGroup,
TradingInput,
TradingCheckbox,
TradingFormGroup,
TradingInputError,
TradingSelect,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
@@ -187,9 +187,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}}
/>
{errors.type && (
<InputError testId="stop-order-error-message-type">
<TradingInputError testId="stop-order-error-message-type">
{errors.type.message}
</InputError>
</TradingInputError>
)}
<Controller
@@ -199,21 +199,21 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
<SideSelector value={field.value} onValueChange={field.onChange} />
)}
/>
<FormGroup label={t('Trigger')} compact={true} labelFor="">
<TradingFormGroup label={t('Trigger')} compact={true} labelFor="">
<Controller
name="triggerDirection"
control={control}
render={({ field }) => {
const { onChange, value } = field;
return (
<RadioGroup
<TradingRadioGroup
name="triggerDirection"
onChange={onChange}
value={value}
orientation="horizontal"
className="mb-2"
>
<Radio
<TradingRadio
value={
Schema.StopOrderTriggerDirection
.TRIGGER_DIRECTION_RISES_ABOVE
@@ -221,7 +221,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
id="triggerDirection-risesAbove"
label={'Rises above'}
/>
<Radio
<TradingRadio
value={
Schema.StopOrderTriggerDirection
.TRIGGER_DIRECTION_FALLS_BELOW
@@ -229,7 +229,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
id="triggerDirection-fallsBelow"
label={'Falls below'}
/>
</RadioGroup>
</TradingRadioGroup>
);
}}
/>
@@ -246,16 +246,17 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
validate: validateAmount(priceStep, 'Price'),
}}
control={control}
render={({ field }) => {
render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
<div className="mb-2">
<Input
<TradingInput
data-testid="triggerPrice"
type="number"
step={priceStep}
appendElement={asset.symbol}
value={value || ''}
hasError={!!fieldState.error}
{...props}
/>
</div>
@@ -263,9 +264,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}}
/>
{errors.triggerPrice && (
<InputError testId="stop-order-error-message-trigger-price">
<TradingInputError testId="stop-order-error-message-trigger-price">
{errors.triggerPrice.message}
</InputError>
</TradingInputError>
)}
</div>
)}
@@ -294,16 +295,17 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
'Trailing percentage offset'
),
}}
render={({ field }) => {
render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
<div className="mb-2">
<Input
<TradingInput
type="number"
step={trailingPercentOffsetStep}
appendElement="%"
data-testid="triggerTrailingPercentOffset"
value={value || ''}
hasError={!!fieldState.error}
{...props}
/>
</div>
@@ -311,9 +313,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}}
/>
{errors.triggerTrailingPercentOffset && (
<InputError testId="stop-order-error-message-trigger-trailing-percent-offset">
<TradingInputError testId="stop-order-error-message-trigger-trailing-percent-offset">
{errors.triggerTrailingPercentOffset.message}
</InputError>
</TradingInputError>
)}
</div>
)}
@@ -324,25 +326,29 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
render={({ field }) => {
const { onChange, value } = field;
return (
<RadioGroup
<TradingRadioGroup
onChange={onChange}
value={value}
orientation="horizontal"
>
<Radio value="price" id="triggerType-price" label={'Price'} />
<Radio
<TradingRadio
value="price"
id="triggerType-price"
label={'Price'}
/>
<TradingRadio
value="trailingPercentOffset"
id="triggerType-trailingPercentOffset"
label={'Trailing Percent Offset'}
/>
</RadioGroup>
</TradingRadioGroup>
);
}}
/>
</FormGroup>
</TradingFormGroup>
<div className="mb-2">
<div className="flex items-start gap-4">
<FormGroup
<TradingFormGroup
labelFor="input-price-quote"
label={t(`Size`)}
className="!mb-0 flex-1"
@@ -358,10 +364,10 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
},
validate: validateAmount(sizeStep, 'Size'),
}}
render={({ field }) => {
render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
<Input
<TradingInput
id="order-size"
className="w-full"
type="number"
@@ -370,16 +376,17 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
onWheel={(e) => e.currentTarget.blur()}
data-testid="order-size"
value={value || ''}
hasError={!!fieldState.error}
{...props}
/>
);
}}
/>
</FormGroup>
<div className="pt-7 leading-10">@</div>
</TradingFormGroup>
<div className="pt-5 leading-10">@</div>
<div className="flex-1">
{type === Schema.OrderType.TYPE_LIMIT ? (
<FormGroup
<TradingFormGroup
labelFor="input-price-quote"
label={t(`Price (${quoteName})`)}
labelAlign="right"
@@ -397,10 +404,10 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
},
validate: validateAmount(priceStep, 'Price'),
}}
render={({ field }) => {
render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
<Input
<TradingInput
id="input-price-quote"
className="w-full"
type="number"
@@ -408,15 +415,16 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
data-testid="order-price"
onWheel={(e) => e.currentTarget.blur()}
value={value || ''}
hasError={!!fieldState.error}
{...props}
/>
);
}}
/>
</FormGroup>
</TradingFormGroup>
) : (
<div
className="text-sm text-right pt-7 leading-10"
className="text-sm text-right pt-5 leading-10"
data-testid="price"
>
{priceFormatted && quoteName
@@ -427,21 +435,21 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
</div>
</div>
{errors.size && (
<InputError testId="stop-order-error-message-size">
<TradingInputError testId="stop-order-error-message-size">
{errors.size.message}
</InputError>
</TradingInputError>
)}
{!errors.size &&
errors.price &&
type === Schema.OrderType.TYPE_LIMIT && (
<InputError testId="stop-order-error-message-price">
<TradingInputError testId="stop-order-error-message-price">
{errors.price.message}
</InputError>
</TradingInputError>
)}
</div>
<div className="mb-2">
<FormGroup
<TradingFormGroup
label={t('Time in force')}
labelFor="select-time-in-force"
compact={true}
@@ -449,11 +457,12 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
<Controller
name="timeInForce"
control={control}
render={({ field }) => (
<Select
render={({ field, fieldState }) => (
<TradingSelect
id="select-time-in-force"
className="w-full"
data-testid="order-tif"
hasError={!!fieldState.error}
{...field}
>
<option
@@ -468,14 +477,14 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
>
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
</option>
</Select>
</TradingSelect>
)}
/>
</FormGroup>
</TradingFormGroup>
{errors.timeInForce && (
<InputError testId="stop-error-message-tif">
<TradingInputError testId="stop-error-message-tif">
{errors.timeInForce.message}
</InputError>
</TradingInputError>
)}
</div>
<div className="flex gap-2 pb-2 justify-between">
@@ -485,29 +494,29 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
render={({ field }) => {
const { onChange: onCheckedChange, value } = field;
return (
<Checkbox
<TradingCheckbox
onCheckedChange={onCheckedChange}
checked={value}
name="expire"
label={<span className="text-xs">{t('Expire')}</span>}
label={t('Expire')}
/>
);
}}
/>
<Checkbox
<TradingCheckbox
name="reduce-only"
checked={true}
disabled={true}
label={
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
<span className="text-xs">{t('Reduce only')}</span>
<>{t('Reduce only')}</>
</Tooltip>
}
/>
</div>
{expire && (
<>
<FormGroup
<TradingFormGroup
label={t('Strategy')}
labelFor="expiryStrategy"
compact={true}
@@ -517,26 +526,26 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
control={control}
render={({ field }) => {
return (
<RadioGroup orientation="horizontal" {...field}>
<Radio
<TradingRadioGroup orientation="horizontal" {...field}>
<TradingRadio
value={
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
}
id="expiryStrategy-submit"
label={'Submit'}
/>
<Radio
<TradingRadio
value={
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
}
id="expiryStrategy-cancel"
label={'Cancel'}
/>
</RadioGroup>
</TradingRadioGroup>
);
}}
/>
</FormGroup>
</TradingFormGroup>
<div className="mb-2">
<Controller
name="expiresAt"
@@ -17,8 +17,8 @@ import type { OrderSubmission } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { mapFormValuesToOrderSubmission } from '../../utils/map-form-values-to-submission';
import {
Checkbox,
InputError,
TradingCheckbox,
TradingInputError,
Intent,
Notification,
Tooltip,
@@ -417,7 +417,7 @@ export const DealTicket = ({
name="postOnly"
control={control}
render={({ field }) => (
<Checkbox
<TradingCheckbox
name="post-only"
checked={!disablePostOnlyCheckbox && field.value}
disabled={disablePostOnlyCheckbox}
@@ -449,7 +449,7 @@ export const DealTicket = ({
name="reduceOnly"
control={control}
render={({ field }) => (
<Checkbox
<TradingCheckbox
name="reduce-only"
checked={!disableReduceOnlyCheckbox && field.value}
disabled={disableReduceOnlyCheckbox}
@@ -483,7 +483,7 @@ export const DealTicket = ({
name="iceberg"
control={control}
render={({ field }) => (
<Checkbox
<TradingCheckbox
name="iceberg"
checked={field.value}
onCheckedChange={field.onChange}
@@ -572,11 +572,11 @@ export const NoWalletWarning = ({
if (isReadOnly) {
return (
<div className="mb-2">
<InputError testId="deal-ticket-error-message-summary">
<TradingInputError testId="deal-ticket-error-message-summary">
{
'You need to connect your own wallet to start trading on this market'
}
</InputError>
</TradingInputError>
</div>
);
}
@@ -613,9 +613,9 @@ const SummaryMessage = memo(
if (error?.message) {
return (
<div className="mb-2">
<InputError testId="deal-ticket-error-message-summary">
<TradingInputError testId="deal-ticket-error-message-summary">
{error?.message}
</InputError>
</TradingInputError>
</div>
);
}
@@ -1,4 +1,8 @@
import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import {
TradingFormGroup,
TradingInput,
TradingInputError,
} from '@vegaprotocol/ui-toolkit';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useRef } from 'react';
@@ -19,24 +23,25 @@ export const ExpirySelector = ({
const dateFormatted = formatForInput(date);
const minDate = formatForInput(date);
return (
<FormGroup
<TradingFormGroup
label={t('Expiry time/date')}
labelFor="expiration"
compact={true}
>
<Input
<TradingInput
data-testid="date-picker-field"
id="expiration"
type="datetime-local"
value={dateFormatted}
onChange={(e) => onSelect(e.target.value)}
min={minDate}
hasError={!!errorMessage}
/>
{errorMessage && (
<InputError testId="deal-ticket-error-message-expiry">
<TradingInputError testId="deal-ticket-error-message-expiry">
{errorMessage}
</InputError>
</TradingInputError>
)}
</FormGroup>
</TradingFormGroup>
);
};
@@ -1,7 +1,7 @@
import {
FormGroup,
InputError,
Select,
TradingFormGroup,
TradingInputError,
TradingSelect,
Tooltip,
SimpleGrid,
} from '@vegaprotocol/ui-toolkit';
@@ -90,12 +90,12 @@ export const TimeInForceSelector = ({
};
return (
<FormGroup
<TradingFormGroup
label={t('Time in force')}
labelFor="select-time-in-force"
compact={true}
>
<Select
<TradingSelect
id="select-time-in-force"
value={value}
onChange={(e) => {
@@ -103,18 +103,19 @@ export const TimeInForceSelector = ({
}}
className="w-full"
data-testid="order-tif"
hasError={!!errorMessage}
>
{options.map(([key, value]) => (
<option key={key} value={value}>
{timeInForceLabel(value)}
</option>
))}
</Select>
</TradingSelect>
{errorMessage && (
<InputError testId="deal-ticket-error-message-tif">
<TradingInputError testId="deal-ticket-error-message-tif">
{renderError(errorMessage)}
</InputError>
</TradingInputError>
)}
</FormGroup>
</TradingFormGroup>
);
};
@@ -1,5 +1,5 @@
import {
InputError,
TradingInputError,
SimpleGrid,
Tooltip,
TradingDropdown,
@@ -178,9 +178,9 @@ export const TypeSelector = ({
value={value}
/>
{errorMessage && (
<InputError testId="deal-ticket-error-message-type">
<TradingInputError testId="deal-ticket-error-message-type">
{renderError(errorMessage as MarketModeValidationType)}
</InputError>
</TradingInputError>
)}
</>
);
+28 -26
View File
@@ -14,14 +14,14 @@ import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import {
Button,
FormGroup,
Input,
InputError,
RichSelect,
TradingFormGroup,
TradingInput,
TradingInputError,
TradingRichSelect,
Notification,
Intent,
ButtonLink,
Select,
TradingSelect,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
@@ -151,7 +151,7 @@ export const DepositForm = ({
noValidate={true}
data-testid="deposit-form"
>
<FormGroup
<TradingFormGroup
label={t('From (Ethereum address)')}
labelFor="ethereum-address"
>
@@ -197,15 +197,17 @@ export const DepositForm = ({
}}
/>
{errors.from?.message && (
<InputError intent="danger">{errors.from.message}</InputError>
<TradingInputError intent="danger">
{errors.from.message}
</TradingInputError>
)}
</FormGroup>
<FormGroup label={t('To (Vega key)')} labelFor="to">
</TradingFormGroup>
<TradingFormGroup label={t('To (Vega key)')} labelFor="to">
<AddressField
pubKeys={pubKeys}
onChange={() => setValue('to', '')}
select={
<Select {...register('to')} id="to" defaultValue="">
<TradingSelect {...register('to')} id="to" defaultValue="">
<option value="" disabled>
{t('Please select')}
</option>
@@ -215,10 +217,10 @@ export const DepositForm = ({
{pk}
</option>
))}
</Select>
</TradingSelect>
}
input={
<Input
<TradingInput
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown
id="to"
@@ -233,12 +235,12 @@ export const DepositForm = ({
}
/>
{errors.to?.message && (
<InputError intent="danger" forInput="to">
<TradingInputError intent="danger" forInput="to">
{errors.to.message}
</InputError>
</TradingInputError>
)}
</FormGroup>
<FormGroup label={t('Asset')} labelFor="asset">
</TradingFormGroup>
<TradingFormGroup label={t('Asset')} labelFor="asset">
<Controller
control={control}
name="asset"
@@ -248,7 +250,7 @@ export const DepositForm = ({
},
}}
render={({ field }) => (
<RichSelect
<TradingRichSelect
data-testid="select-asset"
id={field.name}
name={field.name}
@@ -271,13 +273,13 @@ export const DepositForm = ({
}
/>
))}
</RichSelect>
</TradingRichSelect>
)}
/>
{errors.asset?.message && (
<InputError intent="danger" forInput="asset">
<TradingInputError intent="danger" forInput="asset">
{errors.asset.message}
</InputError>
</TradingInputError>
)}
{isActive && isFaucetable && selectedAsset && (
<UseButton onClick={submitFaucet}>
@@ -296,7 +298,7 @@ export const DepositForm = ({
{t('View asset details')}
</button>
)}
</FormGroup>
</TradingFormGroup>
<FaucetNotification
isActive={isActive}
selectedAsset={selectedAsset}
@@ -308,8 +310,8 @@ export const DepositForm = ({
</div>
)}
{approved && (
<FormGroup label={t('Amount')} labelFor="amount">
<Input
<TradingFormGroup label={t('Amount')} labelFor="amount">
<TradingInput
type="number"
autoComplete="off"
id="amount"
@@ -374,9 +376,9 @@ export const DepositForm = ({
})}
/>
{errors.amount?.message && (
<InputError intent="danger" forInput="amount">
<TradingInputError intent="danger" forInput="amount">
{errors.amount.message}
</InputError>
</TradingInputError>
)}
{selectedAsset && balances && (
<UseButton
@@ -390,7 +392,7 @@ export const DepositForm = ({
{t('Use maximum')}
</UseButton>
)}
</FormGroup>
</TradingFormGroup>
)}
<ApproveNotification
isActive={isActive}
@@ -4,10 +4,10 @@ import { t } from '@vegaprotocol/i18n';
import {
Button,
ButtonLink,
Input,
TradingInput,
Loader,
Radio,
RadioGroup,
TradingRadio,
TradingRadioGroup,
} from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '../../hooks';
import { CUSTOM_NODE_KEY } from '../../types';
@@ -76,7 +76,7 @@ export const NodeSwitcher = ({ closeDialog }: { closeDialog: () => void }) => {
`This app will only work on ${VEGA_ENV}. Select a node to connect to.`
)}
</p>
<RadioGroup
<TradingRadioGroup
value={nodeRadio}
onChange={(value) => setNodeRadio(value)}
>
@@ -112,7 +112,7 @@ export const NodeSwitcher = ({ closeDialog }: { closeDialog: () => void }) => {
/>
</div>
</div>
</RadioGroup>
</TradingRadioGroup>
<div className="mt-4">
<Button
fill={true}
@@ -161,7 +161,7 @@ const CustomRowWrapper = ({
<LayoutRow dataTestId="custom-row">
<div className="flex w-full mb-2">
{nodes.length > 0 && (
<Radio
<TradingRadio
id="node-url-custom"
value={CUSTOM_NODE_KEY}
label={nodeRadio === CUSTOM_NODE_KEY ? '' : t('Other')}
@@ -172,7 +172,7 @@ const CustomRowWrapper = ({
data-testid="custom-node"
className="flex items-center w-full gap-2"
>
<Input
<TradingInput
placeholder="https://"
value={inputText}
hasError={Boolean(error)}
@@ -2,7 +2,7 @@ import type { ApolloError } from '@apollo/client';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { isValidUrl } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Radio } from '@vegaprotocol/ui-toolkit';
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react';
import { CUSTOM_NODE_KEY } from '../../types';
import {
@@ -138,7 +138,7 @@ export const RowData = ({
<>
{id !== CUSTOM_NODE_KEY && (
<div className="break-all" data-testid="node">
<Radio id={`node-url-${id}`} value={url} label={url} />
<TradingRadio id={`node-url-${id}`} value={url} label={url} />
</div>
)}
<LayoutCell
@@ -128,6 +128,9 @@ fragment StopOrderFields on StopOrder {
updatedAt
partyId
marketId
order {
...OrderFields
}
trigger {
... on StopOrderPrice {
price
@@ -34,22 +34,51 @@ export type OrdersUpdateSubscription = { __typename?: 'Subscription', orders?: A
export type OrderSubmissionFieldsFragment = { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null };
export type StopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } };
export type StopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } };
export type StopOrdersQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type StopOrdersQuery = { __typename?: 'Query', stopOrders?: { __typename?: 'StopOrderConnection', edges?: Array<{ __typename?: 'StopOrderEdge', node?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }> | null } | null };
export type StopOrdersQuery = { __typename?: 'Query', stopOrders?: { __typename?: 'StopOrderConnection', edges?: Array<{ __typename?: 'StopOrderEdge', node?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }> | null } | null };
export type StopOrderByIdQueryVariables = Types.Exact<{
stopOrderId: Types.Scalars['ID'];
}>;
export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null };
export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null };
export const OrderUpdateFieldsFragmentDoc = gql`
fragment OrderUpdateFields on OrderUpdate {
id
marketId
type
side
size
status
rejectionReason
price
timeInForce
remaining
expiresAt
createdAt
updatedAt
liquidityProvisionId
peggedOrder {
__typename
reference
offset
}
icebergOrder {
__typename
peakSize
minimumVisibleSize
reservedRemaining
}
}
`;
export const OrderFieldsFragmentDoc = gql`
fragment OrderFields on Order {
id
@@ -85,35 +114,6 @@ export const OrderFieldsFragmentDoc = gql`
}
}
`;
export const OrderUpdateFieldsFragmentDoc = gql`
fragment OrderUpdateFields on OrderUpdate {
id
marketId
type
side
size
status
rejectionReason
price
timeInForce
remaining
expiresAt
createdAt
updatedAt
liquidityProvisionId
peggedOrder {
__typename
reference
offset
}
icebergOrder {
__typename
peakSize
minimumVisibleSize
reservedRemaining
}
}
`;
export const OrderSubmissionFieldsFragmentDoc = gql`
fragment OrderSubmissionFields on OrderSubmission {
marketId
@@ -144,6 +144,9 @@ export const StopOrderFieldsFragmentDoc = gql`
updatedAt
partyId
marketId
order {
...OrderFields
}
trigger {
... on StopOrderPrice {
price
@@ -156,7 +159,8 @@ export const StopOrderFieldsFragmentDoc = gql`
...OrderSubmissionFields
}
}
${OrderSubmissionFieldsFragmentDoc}`;
${OrderFieldsFragmentDoc}
${OrderSubmissionFieldsFragmentDoc}`;
export const OrderByIdDocument = gql`
query OrderById($orderId: ID!) {
orderByID(id: $orderId) {
@@ -9,9 +9,9 @@ import { t } from '@vegaprotocol/i18n';
import { Size } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import {
FormGroup,
Input,
InputError,
TradingFormGroup,
TradingInput,
TradingInputError,
Button,
Dialog,
Icon,
@@ -102,8 +102,12 @@ export const OrderEditDialog = ({
noValidate
>
<div className="flex flex-col md:flex-row gap-4">
<FormGroup label={t('Price')} labelFor="limitPrice" className="grow">
<Input
<TradingFormGroup
label={t('Price')}
labelFor="limitPrice"
className="grow"
>
<TradingInput
type="number"
step={step}
{...register('limitPrice', {
@@ -119,13 +123,13 @@ export const OrderEditDialog = ({
id="limitPrice"
/>
{errors.limitPrice?.message && (
<InputError intent="danger">
<TradingInputError intent="danger">
{errors.limitPrice.message}
</InputError>
</TradingInputError>
)}
</FormGroup>
<FormGroup label={t('Size')} labelFor="size" className="grow">
<Input
</TradingFormGroup>
<TradingFormGroup label={t('Size')} labelFor="size" className="grow">
<TradingInput
type="number"
step={stepSize}
{...register('size', {
@@ -139,9 +143,11 @@ export const OrderEditDialog = ({
id="size"
/>
{errors.size?.message && (
<InputError intent="danger">{errors.size.message}</InputError>
<TradingInputError intent="danger">
{errors.size.message}
</TradingInputError>
)}
</FormGroup>
</TradingFormGroup>
</div>
<Button variant="primary" size="md" type="submit">
{t('Update')}
@@ -296,7 +296,7 @@ export const OrderListTable = memo<
</ButtonLink>
</>
)}
<ActionsDropdown data-testid="market-actions-content">
<ActionsDropdown data-testid="order-actions-content">
<TradingDropdownCopyItem
value={data.id}
text={t('Copy order ID')}
@@ -1,11 +1,13 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { StopOrdersTable } from '../stop-orders-table/stop-orders-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { stopOrdersWithMarketProvider } from '../order-data-provider/stop-orders-data-provider';
import { OrderViewDialog } from '../order-list/order-view-dialog';
import type { Order } from '../order-data-provider';
export interface StopOrdersManagerProps {
partyId: string;
@@ -23,6 +25,7 @@ export const StopOrdersManager = ({
gridProps,
}: StopOrdersManagerProps) => {
const create = useVegaTransactionStore((state) => state.create);
const [viewOrder, setViewOrder] = useState<Order | null>(null);
const variables = { partyId };
const { data, error, reload } = useDataProvider({
@@ -53,14 +56,25 @@ export const StopOrdersManager = ({
);
return (
<StopOrdersTable
rowData={data}
onCancel={cancel}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
suppressAutoSize
overlayNoRowsTemplate={error ? error.message : t('No stop orders')}
{...gridProps}
/>
<>
<StopOrdersTable
rowData={data}
onCancel={cancel}
onView={setViewOrder}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
suppressAutoSize
overlayNoRowsTemplate={error ? error.message : t('No stop orders')}
{...gridProps}
/>
{viewOrder && (
<OrderViewDialog
isOpen={Boolean(viewOrder)}
order={viewOrder}
onChange={() => setViewOrder(null)}
onMarketClick={onMarketClick}
/>
)}
</>
);
};
@@ -4,6 +4,7 @@ import type { PartialDeep } from 'type-fest';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
import userEvent from '@testing-library/user-event';
import {
StopOrdersTable,
type StopOrdersTableProps,
@@ -27,6 +28,7 @@ jest.mock('@vegaprotocol/utils', () => ({
}));
const defaultProps: StopOrdersTableProps = {
onView: jest.fn(),
rowData: [],
onCancel: jest.fn(),
isReadOnly: false,
@@ -104,6 +106,7 @@ const rowData = [
generateStopOrder({
id: 'stop-order-6',
status: Schema.StopOrderStatus.STATUS_TRIGGERED,
order: { id: 'order-id' },
}),
];
@@ -234,4 +237,37 @@ describe('StopOrdersTable', () => {
);
});
});
it('shows actions dropdown only for triggered stop orders', async () => {
await act(async () => {
render(generateJsx({ rowData }));
});
const dropdownMenuButtons = screen.getAllByTestId('dropdown-menu');
expect(dropdownMenuButtons).toHaveLength(1);
dropdownMenuButtons.forEach((dropdownMenuButton) => {
const id = dropdownMenuButton
.closest('[role="row"]')
?.getAttribute('row-id');
expect(rowData.find((row) => row.id === id)?.status).toEqual(
Schema.StopOrderStatus.STATUS_TRIGGERED
);
});
});
it('action dropdown has copy and view order actions', async () => {
const onView = jest.fn();
const user = userEvent.setup();
await act(async () => {
render(generateJsx({ rowData, onView }));
});
const dropdownMenuButtons = screen.getByTestId('dropdown-menu');
dropdownMenuButtons.click();
await user.click(dropdownMenuButtons as HTMLButtonElement);
const menuItems = screen.getAllByRole('menuitem');
expect(menuItems).toHaveLength(2);
expect(menuItems[0]).toHaveTextContent('Copy order ID');
expect(menuItems[1]).toHaveTextContent('View order details');
menuItems[1].click();
expect(onView).toBeCalled();
});
});
@@ -7,7 +7,14 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import {
ActionsDropdown,
ButtonLink,
VegaIcon,
VegaIconNames,
DropdownMenuItem,
TradingDropdownCopyItem,
} from '@vegaprotocol/ui-toolkit';
import type { ForwardedRef } from 'react';
import { memo, useMemo } from 'react';
import {
@@ -28,6 +35,7 @@ import type {
import type { AgGridReact } from 'ag-grid-react';
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
import type { ColDef } from 'ag-grid-community';
import type { Order } from '../order-data-provider';
const defaultColDef = {
resizable: true,
@@ -38,12 +46,13 @@ const defaultColDef = {
export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
onCancel: (order: StopOrder) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onView: (order: Order) => void;
isReadOnly: boolean;
};
export const StopOrdersTable = memo<
StopOrdersTableProps & { ref?: ForwardedRef<AgGridReact> }
>(({ onCancel, onMarketClick, ...props }: StopOrdersTableProps) => {
>(({ onCancel, onView, onMarketClick, ...props }: StopOrdersTableProps) => {
const showAllActions = !props.isReadOnly;
const columnDefs: ColDef[] = useMemo(
() => [
@@ -236,12 +245,32 @@ export const StopOrdersTable = memo<
{t('Cancel')}
</ButtonLink>
)}
{data.status === Schema.StopOrderStatus.STATUS_TRIGGERED &&
data.order && (
<ActionsDropdown data-testid="stop-order-actions-content">
<TradingDropdownCopyItem
value={data.order.id}
text={t('Copy order ID')}
/>
<DropdownMenuItem
key={'view-order'}
data-testid="view-order"
onClick={() =>
data.order &&
onView({ ...data.order, market: data.market })
}
>
<VegaIcon name={VegaIconNames.INFO} size={16} />
{t('View order details')}
</DropdownMenuItem>
</ActionsDropdown>
)}
</div>
);
},
},
],
[onCancel, onMarketClick, props.isReadOnly, showAllActions]
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
);
return (
@@ -2,7 +2,12 @@ import * as Schema from '@vegaprotocol/types';
import type { Account } from '@vegaprotocol/accounts';
import type { MarketWithData } from '@vegaprotocol/markets';
import type { PositionFieldsFragment } from './__generated__/Positions';
import { getMetrics, rejoinPositionData } from './positions-data-providers';
import type { Position } from './positions-data-providers';
import {
getMetrics,
preparePositions,
rejoinPositionData,
} from './positions-data-providers';
import { PositionStatus } from '@vegaprotocol/types';
const accounts = [
@@ -223,4 +228,29 @@ describe('getMetrics && rejoinPositionData', () => {
);
expect(metrics[1].status).toEqual(positions[1].positionStatus);
});
it('sorts and filters positions', () => {
const createPosition = (override?: Partial<Position>) =>
({
marketState: Schema.MarketState.STATE_ACTIVE,
marketCode: 'a',
...override,
} as Position);
const data = [
createPosition(),
createPosition({
marketCode: 'c',
marketState: Schema.MarketState.STATE_CANCELLED,
}),
createPosition({ marketCode: 'd' }),
createPosition({ marketCode: 'b' }),
];
const withoutClosed = preparePositions(data, false);
expect(withoutClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'd']);
const withClosed = preparePositions(data, true);
expect(withClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'c', 'd']);
});
});
@@ -41,6 +41,7 @@ export interface Position {
marketId: string;
marketCode: string;
marketTradingMode: Schema.MarketTradingMode;
marketState: Schema.MarketState;
markPrice: string | undefined;
notional: string | undefined;
openVolume: string;
@@ -119,6 +120,7 @@ export const getMetrics = (
marketId: market.id,
marketCode: market.tradableInstrument.instrument.code,
marketTradingMode: market.tradingMode,
marketState: market.state,
markPrice: marketData ? marketData.markPrice : undefined,
notional: notional
? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
@@ -248,6 +250,26 @@ export const rejoinPositionData = (
return null;
};
export const preparePositions = (metrics: Position[], showClosed: boolean) => {
return sortBy(metrics, 'marketCode').filter((p) => {
if (showClosed) {
return true;
}
if (
[
Schema.MarketState.STATE_ACTIVE,
Schema.MarketState.STATE_PENDING,
Schema.MarketState.STATE_SUSPENDED,
].includes(p.marketState)
) {
return true;
}
return false;
});
};
export const positionsMarketsProvider = makeDerivedDataProvider<
string[],
never,
@@ -265,7 +287,7 @@ export const positionsMarketsProvider = makeDerivedDataProvider<
export const positionsMetricsProvider = makeDerivedDataProvider<
Position[],
Position[],
PositionsQueryVariables & { marketIds: string[] }
PositionsQueryVariables & { marketIds: string[]; showClosed: boolean }
>(
[
(callback, client, variables) =>
@@ -281,10 +303,10 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
marketIds: variables.marketIds,
}),
],
([positions, accounts, marketsData]) => {
([positions, accounts, marketsData], variables) => {
const positionsData = rejoinPositionData(positions, marketsData);
const metrics = getMetrics(positionsData, accounts as Account[] | null);
return sortBy(metrics, 'marketCode');
return preparePositions(metrics, variables.showClosed);
},
(data, delta, previousData) =>
data.filter((row) => {
+4 -2
View File
@@ -16,6 +16,7 @@ interface PositionsManagerProps {
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
gridProps?: ReturnType<typeof useDataGridEvents>;
showClosed?: boolean;
}
export const PositionsManager = ({
@@ -23,6 +24,7 @@ export const PositionsManager = ({
onMarketClick,
isReadOnly,
gridProps,
showClosed = false,
}: PositionsManagerProps) => {
const { pubKeys, pubKey } = useVegaWallet();
const create = useVegaTransactionStore((store) => store.create);
@@ -60,7 +62,7 @@ export const PositionsManager = ({
const { data, error } = useDataProvider({
dataProvider: positionsMetricsProvider,
variables: { partyIds, marketIds: marketIds || [] },
variables: { partyIds, marketIds: marketIds || [], showClosed },
skip: !marketIds,
});
@@ -68,7 +70,7 @@ export const PositionsManager = ({
<PositionsTable
pubKey={pubKey}
pubKeys={pubKeys}
rowData={error ? [] : data}
rowData={data}
onMarketClick={onMarketClick}
onClose={onClose}
isReadOnly={isReadOnly}
@@ -27,6 +27,7 @@ const singleRow: Position = {
marketId: 'string',
marketCode: 'ETHBTC.QM21',
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
marketState: Schema.MarketState.STATE_ACTIVE,
markPrice: '123',
notional: '12300',
openVolume: '100',
+2 -2
View File
@@ -473,8 +473,8 @@ export const PositionsTable = ({
</div>
);
},
minWidth: 75,
maxWidth: 75,
minWidth: 55,
maxWidth: 55,
}
: null,
];
@@ -17,4 +17,17 @@ export const proposalsDataProvider = makeDataProvider<
never,
never,
ProposalsListQueryVariables
>({ query: ProposalsListDocument, getData });
>({
query: ProposalsListDocument,
getData,
/**
* Ignores errors for not found settlement asset for NewMarket proposals.
*
* It can happen that a NewMarket proposal is incomplete and does not contain
* `futureProduct` details. This guard protects against that.
*
* GQL Path: `terms.change.instrument.futureProduct.settlementAsset`
*/
errorPolicyGuard: (errors) =>
errors.every((e) => e.message.match(/failed to get asset for ID/)),
});
@@ -1,5 +1,9 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useTimeToUpgrade } from './use-time-to-upgrade';
import {
ERR_NO_TIME_UNITS,
parseDuration,
useTimeToUpgrade,
} from './use-time-to-upgrade';
jest.mock('./__generated__/BlockStatistics', () => ({
...jest.requireActual('./__generated__/BlockStatistics'),
@@ -8,7 +12,7 @@ jest.mock('./__generated__/BlockStatistics', () => ({
data: {
statistics: {
blockHeight: 1,
blockDuration: 500,
blockDuration: '500ms',
},
},
};
@@ -30,3 +34,25 @@ describe('useTimeToUpgrade', () => {
});
});
});
describe('parseDuration', () => {
it.each([
['1000000ns', 1],
['1000µs', 1],
['1ms', 1],
['1s', 1000],
['1m', 60 * 1000],
['1h', 60 * 60 * 1000],
// below test cases are from vega
['3.3s', 3300],
['4m5s', 4 * 60 * 1000 + 5 * 1000],
['4m5.001s', 4 * 60 * 1000 + 5001],
['5h6m7.001s', 5 * 60 * 60 * 1000 + 6 * 60 * 1000 + 7001],
['8m0.000000001s', 8 * 60 * 1000 + 1 / 1000000],
])('parses %s to %d milliseconds', (input, output) => {
expect(parseDuration(input)).toEqual(output);
});
it('throws an error when given corrupted data', () => {
expect(() => parseDuration('blah')).toThrow(ERR_NO_TIME_UNITS);
});
});
@@ -7,6 +7,52 @@ const DEFAULT_POLLS = 10;
const INTERVAL = 1000;
const durations = [] as number[];
export const ERR_NO_TIME_UNITS = new Error(
'could not parse block duration value - no time units detected'
);
/**
* Parses block duration value and output a number of milliseconds.
* @param input The block duration input from the API, e.g. 4m5.001s
* @returns A number of milliseconds
*/
export const parseDuration = (input: string) => {
// h -> 60*60*1000
// m -> 60*1000
// s -> 1000
// ms -> 1
// µs -> 1/1000
// ns -> 1/1000000
let H = 0;
let M = 0;
let S = 0;
const lessThanSecond = /^[0-9.]+[nµm]*s$/gu.test(input);
const exp = /(?<hours>[0-9.]+h)?(?<minutes>[0-9.]+m)?(?<seconds>[0-9.]+s)?/gu;
const m = exp.exec(input);
const hours = m?.groups?.['hours'];
const minutes = m?.groups?.['minutes'];
const seconds = lessThanSecond ? input : m?.groups?.['seconds'];
if (!lessThanSecond && !hours && !minutes && !seconds) {
throw ERR_NO_TIME_UNITS;
}
if (seconds) {
S = parseFloat(seconds);
if (seconds.includes('ns')) S /= 1000 * 1000;
else if (seconds.includes('µs')) S /= 1000;
else if (seconds.includes('ms')) S *= 1;
else if (seconds.includes('s')) S *= 1000;
}
if (minutes && !lessThanSecond) {
M = parseFloat(minutes) * 60 * 1000;
}
if (hours && !lessThanSecond) {
H = parseFloat(hours) * 60 * 60 * 1000;
}
return H + M + S;
};
const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
const [avg, setAvg] = useState<number | undefined>(undefined);
const { data, startPolling, stopPolling, error } = useBlockStatisticsQuery({
@@ -28,7 +74,11 @@ const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
useEffect(() => {
if (durations.length < polls && data) {
durations.push(parseFloat(data.statistics.blockDuration));
try {
durations.push(parseDuration(data.statistics.blockDuration)); // ms
} catch (err) {
// NOOP - do not add unparsed value to AVG
}
}
if (durations.length === polls) {
const averageBlockDuration = sum(durations) / durations.length; // ms
+5 -1
View File
@@ -1,4 +1,8 @@
{
"name": "@vegaprotocol/react-helpers",
"version": "0.2.5"
"version": "0.2.5",
"peerDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}
+1 -1
View File
@@ -172,7 +172,7 @@ module.exports = {
900: '#F9FAFA',
},
},
danger: '#FF077F',
danger: '#EC003C',
warning: '#FF8700',
success: '#00F780',
},
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "@vegaprotocol/types",
"version": "0.0.4"
"version": "0.0.5"
}
+5 -1
View File
@@ -1,4 +1,8 @@
{
"name": "@vegaprotocol/ui-toolkit",
"version": "0.12.7"
"version": "0.12.8",
"peerDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}
+11 -3
View File
@@ -16,8 +16,8 @@ export * from './form-group';
export * from './healthbar';
export * from './icon';
export * from './indicator';
export * from './input-error';
export * from './input';
export * from './input-error';
export * from './key-value-table';
export * from './link';
export * from './loader';
@@ -48,10 +48,18 @@ export * from './tiny-scroll';
export * from './toast';
export * from './toggle';
export * from './tooltip';
export * from './trading-button';
export * from './trading-dropdown';
export * from './traffic-light';
export * from './vega-icons';
export * from './vega-logo';
export * from './viewing-as-user';
export * from './pill';
// Trading specific components
export * from './trading-button';
export * from './trading-checkbox';
export * from './trading-dropdown';
export * from './trading-form-group';
export * from './trading-input-error';
export * from './trading-input';
export * from './trading-radio-group';
export * from './trading-select';
@@ -0,0 +1,40 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { TradingCheckbox } from './checkbox';
describe('Checkbox', () => {
it('should render checkbox with label successfully', () => {
render(<TradingCheckbox label="test" />);
expect(screen.getByText('test')).toBeInTheDocument();
});
it('should render a checked checkbox if specified in state', () => {
render(<TradingCheckbox label="label" checked={true} />);
expect(screen.getByTestId(/icon-/)).toBeInTheDocument();
});
it('should render an unchecked checkbox if specified in state', () => {
render(<TradingCheckbox label="unchecked" checked={false} />);
expect(screen.queryByTestId(/icon-/)).not.toBeInTheDocument();
});
it('should render an indeterminate checkbox if specified in state', () => {
render(<TradingCheckbox label="indeterminate" checked="indeterminate" />);
expect(screen.getByTestId('indeterminate-icon')).toBeInTheDocument();
});
it('fires callback on change if provided', () => {
const callback = jest.fn();
render(
<TradingCheckbox
name="test"
label="onchange"
onCheckedChange={callback}
/>
);
const checkbox = screen.getByText('onchange');
fireEvent.click(checkbox);
expect(callback).toHaveBeenCalled();
});
});
@@ -0,0 +1,38 @@
import type { Meta, StoryFn } from '@storybook/react';
import type { TradingCheckboxProps } from './checkbox';
import { TradingCheckbox } from './checkbox';
export default {
component: TradingCheckbox,
title: 'Checkbox',
} as Meta<typeof TradingCheckbox>;
const Template: StoryFn<TradingCheckboxProps> = (args) => (
<TradingCheckbox {...args} />
);
export const Default = Template.bind({});
Default.args = {
name: 'default',
label: 'Regular checkbox',
};
export const Overflow = Template.bind({});
Overflow.args = {
name: 'overflow',
label:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
};
export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
label: 'Disabled',
};
export const Indeterminate = Template.bind({});
Indeterminate.args = {
name: 'default',
checked: 'indeterminate',
label: 'Indeterminate checkbox',
};
@@ -0,0 +1,63 @@
import { VegaIcon, VegaIconNames } from '../icon';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import classNames from 'classnames';
import type { ReactNode } from 'react';
type CheckedState = boolean | 'indeterminate';
export interface TradingCheckboxProps {
checked?: CheckedState;
label?: ReactNode;
name?: string;
onCheckedChange?: (checked: CheckedState) => void;
disabled?: boolean;
}
export const TradingCheckbox = ({
checked,
label,
name,
onCheckedChange,
disabled = false,
}: TradingCheckboxProps) => {
const rootClasses = classNames(
'relative flex justify-center items-center w-3 h-3',
'border rounded-sm overflow-hidden',
'border-vega-clight-500 dark:border-vega-cdark-500',
'aria-checked:border-vega-clight-400 dark:aria-checked:border-vega-cdark-400',
'disabled:border-vega-clight-600 dark:disabled:border-vega-cdark-600',
'bg-vega-clight-700 dark:bg-vega-cdark-700'
);
return (
<div className="flex gap-1.5 items-center">
<CheckboxPrimitive.Root
name={name}
id={name}
className={rootClasses}
checked={checked}
onCheckedChange={onCheckedChange}
disabled={disabled}
data-testid={name}
>
<CheckboxPrimitive.CheckboxIndicator className="flex justify-center items-center w-3 h-3">
{checked === 'indeterminate' ? (
<span
data-testid="indeterminate-icon"
className="absolute w-[8px] h-[2px] bg-vega-clight-50 dark:bg-vega-cdark-50"
/>
) : (
<VegaIcon name={VegaIconNames.TICK} size={10} />
)}
</CheckboxPrimitive.CheckboxIndicator>
</CheckboxPrimitive.Root>
<label
htmlFor={name}
className={classNames('text-xs flex-1', {
'text-vega-clight-200 dark:text-vega-cdark-200': disabled,
})}
>
{label}
</label>
</div>
);
};
@@ -0,0 +1 @@
export * from './checkbox';
@@ -0,0 +1,23 @@
import { render, screen } from '@testing-library/react';
import { TradingFormGroup } from './form-group';
describe('FormGroup', () => {
it('should render label if given a label', () => {
render(
<TradingFormGroup label="label" labelFor="test">
<input id="test"></input>
</TradingFormGroup>
);
expect(screen.getByLabelText('label')).toBeInTheDocument();
});
it('should render children', () => {
render(
<TradingFormGroup label="label" labelFor="test">
<input data-testid="foo" id="test"></input>
</TradingFormGroup>
);
expect(screen.getByTestId('foo')).toBeInTheDocument();
});
});
@@ -0,0 +1,53 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
export interface TradingFormGroupProps {
children: ReactNode;
className?: string;
label: string | ReactNode; // For accessibility reasons this must always be set for screen readers. If you want it to not show, then use the hideLabel prop"
labelFor: string; // Same as above
hideLabel?: boolean;
disabled?: boolean;
labelDescription?: string;
labelAlign?: 'left' | 'right';
compact?: boolean;
}
export const TradingFormGroup = ({
children,
className,
label,
labelFor,
labelDescription,
labelAlign = 'left',
hideLabel = false,
compact = false,
disabled = false,
}: TradingFormGroupProps) => {
const wrapperClasses = classNames(
'relative',
{
'mb-2': compact,
'mb-4': !compact,
},
className
);
const labelClasses = classNames('block mb-2 text-xs', {
'text-right': labelAlign === 'right',
'sr-only': hideLabel,
'text-muted': disabled,
});
return (
<div data-testid="form-group" className={wrapperClasses}>
{label && (
<label htmlFor={labelFor} className={labelClasses}>
{label}
{labelDescription && (
<div className="font-light mt-1">{labelDescription}</div>
)}
</label>
)}
{children}
</div>
);
};
@@ -0,0 +1,47 @@
import type { StoryFn, Meta } from '@storybook/react';
import { TradingInput } from '../trading-input';
import type { TradingFormGroupProps } from './form-group';
import { TradingFormGroup } from './form-group';
export default {
component: TradingFormGroup,
title: 'FormGroup',
argTypes: {
label: {
type: 'string',
},
labelFor: {
type: 'string',
},
labelDescription: {
type: 'string',
},
className: {
type: 'string',
},
hasError: {
type: 'boolean',
},
disabled: {
type: 'boolean',
},
},
} as Meta;
const Template: StoryFn<TradingFormGroupProps> = (args) => (
<TradingFormGroup {...args}>
<TradingInput id="labelFor" />
</TradingFormGroup>
);
export const Default = Template.bind({});
Default.args = {
label: 'Label',
labelFor: 'labelFor',
};
export const WithLabelDescription = Template.bind({});
WithLabelDescription.args = {
label: 'Label',
labelFor: 'labelFor',
labelDescription: 'Description text',
};
@@ -0,0 +1 @@
export * from './form-group';
@@ -0,0 +1 @@
export * from './input-error';
@@ -0,0 +1,10 @@
import { render } from '@testing-library/react';
import { TradingInputError } from './input-error';
describe('InputError', () => {
it('should render successfully', () => {
const { baseElement } = render(<TradingInputError />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,20 @@
import type { StoryFn, Meta } from '@storybook/react';
import { TradingInputError } from './input-error';
export default {
component: TradingInputError,
title: 'InputError',
} as Meta;
const Template: StoryFn = (args) => <TradingInputError {...args} />;
export const Danger = Template.bind({});
Danger.args = {
children: 'An error that might have happened',
};
export const Warning = Template.bind({});
Warning.args = {
intent: 'warning',
children: 'Something that might be an issue',
};
@@ -0,0 +1,42 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
interface TradingInputErrorProps extends HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
intent?: 'danger' | 'warning';
forInput?: string;
testId?: string;
}
export const TradingInputError = ({
intent = 'danger',
children,
forInput,
testId,
className,
...props
}: TradingInputErrorProps) => {
const effectiveClassName = classNames(
'text-xs flex items-center first-letter:uppercase',
'mt-2',
{
'border-danger': intent === 'danger',
'border-warning': intent === 'warning',
},
{
'text-warning': intent === 'warning',
'text-danger': intent === 'danger',
}
);
return (
<div
data-testid={testId || 'input-error-text'}
aria-describedby={forInput}
className={classNames(effectiveClassName, className)}
{...props}
role="alert"
>
{children}
</div>
);
};
@@ -0,0 +1 @@
export * from './input';
@@ -0,0 +1,10 @@
import { render } from '@testing-library/react';
import { TradingInput } from './input';
describe('Input', () => {
it('should render successfully', () => {
const { baseElement } = render(<TradingInput />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,83 @@
import type { StoryFn, Meta } from '@storybook/react';
import { TradingInput } from './input';
import { FormGroup } from '../form-group';
export default {
component: TradingInput,
title: 'Input',
} as Meta;
const Template: StoryFn = (args) => (
<FormGroup label="Hello" labelFor={args.id}>
<TradingInput value="I type words" {...args} />
</FormGroup>
);
const customElementPlaceholder = (
<span
style={{
fontFamily: 'monospace',
backgroundColor: 'grey',
padding: '4px',
}}
>
Ω
</span>
);
export const Default = Template.bind({});
Default.args = {
id: 'input-default',
};
export const WithError = Template.bind({});
WithError.args = {
hasError: true,
id: 'input-has-error',
};
export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
id: 'input-disabled',
};
export const TypeDate = Template.bind({});
TypeDate.args = {
type: 'date',
id: 'input-date',
};
export const TypeDateTime = Template.bind({});
TypeDateTime.args = {
type: 'datetime-local',
id: 'input-datetime-local',
min: '2022-09-05T11:29:17',
max: '2023-09-05T10:29:49',
};
export const IconPrepend = Template.bind({});
IconPrepend.args = {
prependIconName: 'search',
id: 'input-icon-prepend',
};
export const IconAppend = Template.bind({});
IconAppend.args = {
value: 'I type words and even more words',
appendIconName: 'search',
id: 'input-icon-append',
};
export const ElementPrepend = Template.bind({});
ElementPrepend.args = {
value: '<- custom element',
prependElement: customElementPlaceholder,
id: 'input-element-prepend',
};
export const ElementAppend = Template.bind({});
ElementAppend.args = {
value: 'custom element ->',
appendElement: customElementPlaceholder,
id: 'input-element-append',
};
@@ -0,0 +1,174 @@
import type { InputHTMLAttributes, ReactNode } from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import type { IconName } from '../icon';
import { Icon } from '../icon';
import { defaultFormElement } from '../../utils/shared';
type InputRootProps = InputHTMLAttributes<HTMLInputElement> & {
hasError?: boolean;
disabled?: boolean;
className?: string;
};
type NoPrepend = {
prependIconName?: never;
prependIconDescription?: string;
prependElement?: never;
};
type NoAppend = {
appendIconName?: never;
appendIconDescription?: string;
appendElement?: never;
};
type InputPrepend = NoAppend &
(
| NoPrepend
| {
prependIconName: IconName;
prependIconDescription?: string;
prependElement?: never;
}
| {
prependIconName?: never;
prependIconDescription?: never;
prependElement: ReactNode;
}
);
type InputAppend = NoPrepend &
(
| NoAppend
| {
appendIconName: IconName;
appendIconDescription?: string;
appendElement?: never;
}
| {
appendIconName?: never;
appendIconDescription?: never;
appendElement: ReactNode;
}
);
type AffixProps = InputPrepend | InputAppend;
export type TradingInputProps = InputRootProps & AffixProps;
export const tradingInputStyle = ({
style,
disabled,
}: {
style?: React.CSSProperties;
disabled?: boolean;
}) =>
disabled
? {
...style,
backgroundImage:
'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAAXNSR0IArs4c6QAAACNJREFUGFdjtLS0/M8ABcePH2eEsRlJl4BpBdHIuuFmEi0BABqjEQVjx/LTAAAAAElFTkSuQmCC)',
}
: style;
const getAffixElement = ({
prependElement,
prependIconName,
prependIconDescription,
appendElement,
appendIconName,
appendIconDescription,
}: Pick<TradingInputProps, keyof AffixProps>) => {
const position = prependIconName || prependElement ? 'pre' : 'post';
const className = classNames(
['fill-black dark:fill-white', 'absolute', 'z-10'],
{
'left-3': position === 'pre',
'right-3': position === 'post',
}
);
const element = prependElement || appendElement;
const iconName = prependIconName || appendIconName;
const iconDescription = prependIconDescription || appendIconDescription;
if (element) {
return <div className={className}>{element}</div>;
}
if (iconName) {
return (
<Icon
name={iconName}
className={className}
aria-label={iconDescription}
aria-hidden={!iconDescription}
/>
);
}
return null;
};
export const TradingInput = forwardRef<HTMLInputElement, TradingInputProps>(
(
{
prependIconName,
prependIconDescription,
appendIconName,
appendIconDescription,
prependElement,
appendElement,
className,
hasError,
...props
},
ref
) => {
const hasPrepended = !!(prependIconName || prependElement);
const hasAppended = !!(appendIconName || appendElement);
const inputClassName = classNames(
'appearance-none dark:color-scheme-dark px-3 h-8',
className,
{
'pl-9': hasPrepended,
'pr-9': hasAppended,
}
);
const input = (
<input
{...props}
ref={ref}
className={classNames(
defaultFormElement(hasError, props.disabled),
inputClassName
)}
/>
);
const element = getAffixElement({
prependIconName,
prependIconDescription,
appendIconName,
appendIconDescription,
prependElement,
appendElement,
});
if (element) {
return (
<div className="flex items-center relative">
{hasPrepended && element}
{input}
{hasAppended && element}
</div>
);
}
return input;
}
);
@@ -0,0 +1 @@
export * from './radio-group';
@@ -0,0 +1,22 @@
import type { StoryFn, Meta } from '@storybook/react';
import type { TradingRadioGroupProps } from './radio-group';
import { TradingRadioGroup, TradingRadio } from './radio-group';
export default {
component: TradingRadioGroup,
title: 'RadioGroup',
} as Meta;
const Template: StoryFn<TradingRadioGroupProps> = (args) => (
<TradingRadioGroup {...args}>
<TradingRadio id="item-1" value="1" label="Item 1" />
<TradingRadio id="item-2" value="2" label="Item 2" />
<TradingRadio id="item-3" value="3" label="Disabled item" disabled={true} />
</TradingRadioGroup>
);
export const Vertical = Template.bind({});
export const Horizontal = Template.bind({});
Horizontal.args = {
orientation: 'horizontal',
};
@@ -0,0 +1,99 @@
import { forwardRef } from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import classNames from 'classnames';
import type { ReactNode } from 'react';
export interface TradingRadioGroupProps {
name?: string;
children: ReactNode;
defaultValue?: string;
value?: string;
orientation?: 'horizontal' | 'vertical';
onChange?: (value: string) => void;
className?: string;
}
export const TradingRadioGroup = forwardRef<
HTMLDivElement,
TradingRadioGroupProps
>(
(
{
children,
name,
value,
orientation = 'vertical',
onChange,
className,
}: TradingRadioGroupProps,
ref
) => {
const groupClasses = classNames(
'flex text-sm',
{
'flex-col gap-2': orientation === 'vertical',
'flex-row gap-4': orientation === 'horizontal',
},
className
);
return (
<RadioGroupPrimitive.Root
ref={ref}
name={name}
value={value}
onValueChange={onChange}
orientation={orientation}
className={groupClasses}
>
{children}
</RadioGroupPrimitive.Root>
);
}
);
interface RadioProps {
id: string;
value: string;
label: string;
disabled?: boolean;
}
export const TradingRadio = ({ id, value, label, disabled }: RadioProps) => {
const wrapperClasses = classNames('flex items-center gap-1.5 text-xs');
const itemClasses = classNames(
'flex justify-center items-center',
'w-3 h-3 rounded-full border',
'border-vega-clight-500 dark:border-vega-cdark-500',
'aria-checked:border-vega-clight-400 dark:aria-checked:border-vega-cdark-400',
'disabled:border-vega-clight-600 dark:disabled:border-vega-cdark-600',
'bg-vega-clight-700 dark:bg-vega-cdark-700'
);
const indicatorClasses = classNames(
'block w-2.5 h-2.5 border-2 rounded-full',
'bg-vega-clight-50 dark:bg-vega-cdark-50',
'border-vega-clight-700 dark:border-vega-cdark-700'
);
return (
<div className={wrapperClasses}>
<RadioGroupPrimitive.Item
value={value}
className={itemClasses}
id={id}
data-testid={id}
disabled={disabled}
>
<RadioGroupPrimitive.Indicator className={indicatorClasses} />
</RadioGroupPrimitive.Item>
<label
htmlFor={id}
className={
disabled
? 'text-vega-clight-200 dark:text-vega-cdark-200'
: 'cursor-pointer'
}
>
{label}
</label>
</div>
);
};
@@ -0,0 +1 @@
export * from './select';
@@ -0,0 +1,37 @@
import { render } from '@testing-library/react';
import { TradingRichSelect, TradingSelect, TradingOption } from './select';
describe('Select', () => {
it('should render successfully', () => {
const { baseElement } = render(<TradingSelect />);
expect(baseElement).toBeTruthy();
});
});
describe('RichSelect', () => {
it('should render select element with placeholder when no value is pre-selected', async () => {
const { findByTestId } = render(
<TradingRichSelect placeholder={'Select'}>
<TradingOption value={'1'}>1</TradingOption>
<TradingOption value={'2'}>2</TradingOption>
</TradingRichSelect>
);
const btn = (await findByTestId(
'rich-select-trigger'
)) as HTMLButtonElement;
expect(btn.textContent).toEqual('Select');
});
it('should render select element with pre-selected value', async () => {
const { findByTestId } = render(
<TradingRichSelect placeholder={'Select'} value={'1'}>
<TradingOption value={'1'}>1</TradingOption>
<TradingOption value={'2'}>2</TradingOption>
</TradingRichSelect>
);
const btn = (await findByTestId(
'rich-select-trigger'
)) as HTMLButtonElement;
expect(btn.textContent).toEqual('1');
});
});
@@ -0,0 +1,92 @@
import type { StoryFn, Meta } from '@storybook/react';
import { TradingOption, TradingSelect, TradingRichSelect } from './select';
import { FormGroup } from '../form-group';
export default {
component: TradingSelect,
title: 'Select',
} as Meta;
const Template: StoryFn = (args) => (
<FormGroup label="Select an option" labelFor={args.id}>
<TradingSelect {...args}>
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
</TradingSelect>
</FormGroup>
);
const RichSelectTemplate: StoryFn = ({ placeholder, ...props }) => (
<FormGroup label="Select an option" labelFor={props.id}>
<TradingRichSelect placeholder={placeholder} {...props} />
</FormGroup>
);
export const Default = Template.bind({});
Default.args = {
id: 'select-default',
};
export const WithError = Template.bind({});
WithError.args = {
id: 'select-has-error',
hasError: true,
};
export const Disabled = Template.bind({});
Disabled.args = {
id: 'select-disabled',
disabled: true,
};
export const RichDefaultSelect = RichSelectTemplate.bind({});
RichDefaultSelect.args = {
id: 'rich',
name: 'rich',
placeholder: 'Select an option',
onValueChange: (v: string) => {
// eslint-disable-next-line no-console
console.log(v);
},
children: (
<>
<TradingOption value="1">
<div className="flex flex-col justify-start items-start">
<span>Option One</span>
<span className="text-xs">First option</span>
</div>
</TradingOption>
<TradingOption value="2">
<div className="flex flex-col justify-start items-start">
<span>Option Two</span>
<span className="text-xs">Second option</span>
</div>
</TradingOption>
<TradingOption value="3">
<div className="flex flex-col justify-start items-start">
<span>Option Three</span>
<span className="text-xs">Third option</span>
</div>
</TradingOption>
<TradingOption value="4">
<div className="flex flex-col justify-start items-start">
<span>Option Four</span>
<span className="text-xs">Fourth option</span>
</div>
</TradingOption>
<TradingOption value="5">
<div className="flex flex-col justify-start items-start">
<span>Option Five</span>
<span className="text-xs">Fifth option</span>
</div>
</TradingOption>
<TradingOption value="6">
<div className="flex flex-col justify-start items-start">
<span>Option Six</span>
<span className="text-xs">Sixth option</span>
</div>
</TradingOption>
</>
),
};
@@ -0,0 +1,129 @@
import type { Ref, SelectHTMLAttributes } from 'react';
import { useRef } from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import { Icon } from '..';
import { defaultSelectElement } from '../../utils/shared';
import * as SelectPrimitive from '@radix-ui/react-select';
export interface TradingSelectProps
extends SelectHTMLAttributes<HTMLSelectElement> {
hasError?: boolean;
className?: string;
value?: string | number;
children?: React.ReactNode;
}
export const TradingSelect = forwardRef<HTMLSelectElement, TradingSelectProps>(
({ className, hasError, ...props }, ref) => (
<div className="flex items-center relative">
<select
ref={ref}
{...props}
className={classNames(
defaultSelectElement(hasError, props.disabled),
className,
'appearance-none rounded-md'
)}
/>
<Icon
name="chevron-down"
className="absolute right-4 z-10 pointer-events-none"
/>
</div>
)
);
export type TradingRichSelectProps = React.ComponentProps<
typeof SelectPrimitive.Root
> & {
placeholder: string;
hasError?: boolean;
id?: string;
'data-testid'?: string;
};
export const TradingRichSelect = forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
TradingRichSelectProps
>(({ id, children, placeholder, hasError, ...props }, forwardedRef) => {
const containerRef = useRef<HTMLDivElement>();
const contentRef = useRef<HTMLDivElement>();
return (
<div
ref={containerRef as Ref<HTMLDivElement>}
className="flex items-center relative"
>
<SelectPrimitive.Root {...props} defaultOpen={false}>
<SelectPrimitive.Trigger
data-testid={props['data-testid'] || 'rich-select-trigger'}
className={classNames(
defaultSelectElement(hasError, props.disabled),
'rounded-md pl-2 pr-11',
'max-w-full overflow-hidden break-all'
)}
id={id}
ref={forwardedRef}
>
<SelectPrimitive.Value placeholder={placeholder} />
<SelectPrimitive.Icon className={classNames('absolute right-4')}>
<Icon name="chevron-down" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal container={containerRef.current}>
<SelectPrimitive.Content
ref={contentRef as Ref<HTMLDivElement>}
className={classNames(
'relative',
'z-20',
'bg-white dark:bg-black',
'border border-neutral-500 focus:border-black dark:focus:border-white rounded',
'overflow-hidden',
'shadow-lg'
)}
position={'item-aligned'}
side={'bottom'}
align={'center'}
>
<SelectPrimitive.ScrollUpButton className="flex items-center justify-center py-1 absolute w-full h-6 z-20 bg-gradient-to-t from-transparent to-neutral-50 dark:to-neutral-900">
<Icon name="chevron-up" />
</SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
<SelectPrimitive.ScrollDownButton className="flex items-center justify-center py-1 absolute bottom-0 w-full h-6 z-20 bg-gradient-to-b from-transparent to-neutral-50 dark:to-neutral-900">
<Icon name="chevron-down" />
</SelectPrimitive.ScrollDownButton>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
</div>
);
});
export const TradingOption = forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentProps<typeof SelectPrimitive.Item>
>(({ children, className, ...props }, forwardedRef) => (
<SelectPrimitive.Item
data-testid="rich-select-option"
className={classNames(
'relative',
'text-black dark:text-white',
'cursor-pointer outline-none',
'hover:bg-neutral-100 dark:hover:bg-neutral-800',
'focus:bg-neutral-100 dark:focus:bg-neutral-800',
'pl-2 py-2',
'pr-12',
'w-full',
'text-sm',
'data-selected:bg-vega-yellow dark:data-selected:text-black dark:data-selected:bg-vega-yellow',
className
)}
{...props}
ref={forwardedRef}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator className="absolute right-4 top-[50%] translate-y-[-50%]">
<Icon name="tick" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
));
+10 -8
View File
@@ -1,18 +1,20 @@
import classnames from 'classnames';
export const defaultSelectElement = (hasError?: boolean) =>
classnames(defaultFormElement(hasError), 'pr-10 dark:bg-black');
export const defaultSelectElement = (hasError?: boolean, disabled?: boolean) =>
classnames(defaultFormElement(hasError, disabled), 'pr-10 min-h-8 py-1');
export const defaultFormElement = (hasError?: boolean) =>
export const defaultFormElement = (hasError?: boolean, disabled?: boolean) =>
classnames(
'flex items-center w-full text-sm',
'p-2 rounded whitespace-nowrap text-ellipsis overflow-hidden',
'bg-transparent',
'border',
'focus:border-vega-light-300 dark:focus:border-vega-dark-300',
'disabled:opacity-60',
'focus:border-vega-clight-400 dark:focus:border-vega-cdark-400',
{
'border-vega-pink text-vega-pink': hasError,
'border-vega-light-200 dark:border-vega-dark-200': !hasError,
'bg-vega-clight-700 dark:bg-vega-cdark-700': !disabled && !hasError,
'bg-transparent': disabled || hasError,
'border-vega-clight-600 dark:border-vega-cdark-600': disabled,
'border-vega-red-500': !disabled && hasError,
'border-vega-clight-500 dark:border-vega-cdark-500':
!disabled && !hasError,
}
);
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "@vegaprotocol/utils",
"version": "0.0.7",
"version": "0.0.8",
"type": "commonjs"
}
@@ -2,11 +2,11 @@ import classNames from 'classnames';
import { create } from 'zustand';
import {
Dialog,
FormGroup,
Input,
Intent,
Pill,
TradingButton,
TradingFormGroup,
TradingInput,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
@@ -421,17 +421,17 @@ const CustomUrlInput = ({
<VegaIcon name={VegaIconNames.ARROW_LEFT} /> {t('Go back')}
</button>
</div>
<FormGroup
<TradingFormGroup
labelFor="wallet-url"
label={t('Custom wallet location')}
hideLabel
>
<Input
<TradingInput
value={walletUrl}
onChange={(e) => setWalletUrl(e.target.value)}
name="wallet-url"
/>
</FormGroup>
</TradingFormGroup>
<ConnectionOption
disabled={!isDesktopWalletRunning}
type="jsonRpc"
@@ -1,8 +1,8 @@
import { t } from '@vegaprotocol/i18n';
import {
FormGroup,
Input,
InputError,
TradingFormGroup,
TradingInput,
TradingInputError,
Intent,
TradingButton,
VegaIcon,
@@ -60,8 +60,8 @@ export function ViewConnectorForm({
'Browse from the perspective of another Vega user in read-only mode.'
)}
</p>
<FormGroup label={t('Vega Pubkey')} labelFor="address">
<Input
<TradingFormGroup label={t('Vega Pubkey')} labelFor="address">
<TradingInput
{...register('address', {
required: t('Required'),
validate: validatePubkey,
@@ -71,9 +71,11 @@ export function ViewConnectorForm({
type="text"
/>
{errors.address?.message && (
<InputError intent="danger">{errors.address.message}</InputError>
<TradingInputError intent="danger">
{errors.address.message}
</TradingInputError>
)}
</FormGroup>
</TradingFormGroup>
<TradingButton
data-testid="connect"
intent={Intent.Info}
+23 -17
View File
@@ -12,11 +12,11 @@ import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import {
Button,
FormGroup,
Input,
InputError,
TradingFormGroup,
TradingInput,
TradingInputError,
Notification,
RichSelect,
TradingRichSelect,
ExternalLink,
Intent,
} from '@vegaprotocol/ui-toolkit';
@@ -150,7 +150,7 @@ export const WithdrawForm = ({
field: ControllerRenderProps<FormFields, 'asset'>;
}) => {
return (
<RichSelect
<TradingRichSelect
data-testid="select-asset"
id="asset"
name="asset"
@@ -170,7 +170,7 @@ export const WithdrawForm = ({
balance={<AssetBalance asset={a} />}
/>
))}
</RichSelect>
</TradingRichSelect>
);
};
@@ -193,7 +193,7 @@ export const WithdrawForm = ({
noValidate={true}
data-testid="withdraw-form"
>
<FormGroup label={t('Asset')} labelFor="asset">
<TradingFormGroup label={t('Asset')} labelFor="asset">
<Controller
control={control}
name="asset"
@@ -205,10 +205,12 @@ export const WithdrawForm = ({
render={renderAssetsSelector}
/>
{errors.asset?.message && (
<InputError intent="danger">{errors.asset.message}</InputError>
<TradingInputError intent="danger">
{errors.asset.message}
</TradingInputError>
)}
</FormGroup>
<FormGroup
</TradingFormGroup>
<TradingFormGroup
label={t('To (Ethereum address)')}
labelFor="ethereum-address"
>
@@ -218,15 +220,17 @@ export const WithdrawForm = ({
clearErrors('to');
}}
/>
<Input
<TradingInput
id="ethereum-address"
data-testid="eth-address-input"
{...register('to', { validate: { required, ethereumAddress } })}
/>
{errors.to?.message && (
<InputError intent="danger">{errors.to.message}</InputError>
<TradingInputError intent="danger">
{errors.to.message}
</TradingInputError>
)}
</FormGroup>
</TradingFormGroup>
{selectedAsset && threshold && (
<div className="mb-4">
<WithdrawLimits
@@ -238,8 +242,8 @@ export const WithdrawForm = ({
/>
</div>
)}
<FormGroup label={t('Amount')} labelFor="amount">
<Input
<TradingFormGroup label={t('Amount')} labelFor="amount">
<TradingInput
data-testid="amount-input"
type="number"
autoComplete="off"
@@ -259,7 +263,9 @@ export const WithdrawForm = ({
})}
/>
{errors.amount?.message && (
<InputError intent="danger">{errors.amount.message}</InputError>
<TradingInputError intent="danger">
{errors.amount.message}
</TradingInputError>
)}
{selectedAsset && (
<UseButton
@@ -282,7 +288,7 @@ export const WithdrawForm = ({
/>
</div>
)}
</FormGroup>
</TradingFormGroup>
<Button
data-testid="submit-withdrawal"
type="submit"
+1 -1
View File
@@ -166,7 +166,7 @@
"babel-jest": "29.4.3",
"babel-loader": "8.1.0",
"css-loader": "^6.4.0",
"cypress": "^12.2.0",
"cypress": "12.17.0",
"cypress-mochawesome-reporter": "^3.3.0",
"cypress-real-events": "^1.8.1",
"dotenv": "^16.0.1",
-1
View File
@@ -53,7 +53,6 @@ for the a given Ethereum wallet/address/key:
- **must** see how many tokens in each tranche are locked <a name="1005-VEST-025" href="#1005-VEST-025">1005-VEST-025</a>
- **must** see how many tokens in each tranche are redeemable <a name="1005-VEST-026" href="#1005-VEST-026">1005-VEST-026</a>
- **must** see an option to redeem from tranche <a name="1005-VEST-027" href="#1005-VEST-027">1005-VEST-027</a>
- **must** be warned if amount that can be redeemed from that tranche is greater than the un-associated balance for that Eth key (because this will cause the redeem function to fail) <a name="1005-VEST-028" href="#1005-VEST-028">1005-VEST-028</a>
- **should** see how many tokens I'd need to disassociate to be able to run the redeem function (this should be rounded up to avoid the transaction failing due to more tokens having unlocked since the user looked at the form)
- **should** see link to [disassociate](1004-ASSO-associate.md)
+5 -1
View File
@@ -51,4 +51,8 @@
- **Must** be able to see if your realised PnL was affected by loss socialisation (<a name="7004-POSI-018" href="#7004-POSI-018">7004-POSI-018</a>)
- **Must** Must be able to see what type of product the position was opened on (<a name="7004-POSI-019" href="#7004-POSI-019">7004-POSI-019</a>)
- **Must** be able to see what type of product the position was opened on (<a name="7004-POSI-019" href="#7004-POSI-019">7004-POSI-019</a>)
- **Must** not see positions on markets which are closed (<a name="7004-POSI-020" href="#7004-POSI-020">7004-POSI-020</a>)
- **Must** be able to show closed markets (<a name="7004-POSI-021" href="#7004-POSI-021">7004-POSI-021</a>)
+28 -27
View File
@@ -2881,9 +2881,9 @@
globby "^11.0.4"
"@cypress/request@^2.88.10":
version "2.88.10"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==
version "2.88.12"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590"
integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@@ -2898,9 +2898,9 @@
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
performance-now "^2.1.0"
qs "~6.5.2"
qs "~6.10.3"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tough-cookie "^4.1.3"
tunnel-agent "^0.6.0"
uuid "^8.3.2"
@@ -8247,9 +8247,9 @@
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
"@types/node@^14.14.31":
version "14.18.32"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014"
integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==
version "14.18.55"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.55.tgz#c60ad83c7d87c2d933cc4c1cb7d54d98ae50e460"
integrity sha512-PiNZnJDie6lgSWfjWYcQ8KWrEHp0bGv1WgnQAUuaao/HpUBKNX+HXubScoMRdLXBuovbte0djGtsxiWScvlQUQ==
"@types/node@^16.0.0":
version "16.18.37"
@@ -12337,10 +12337,10 @@ cypress-real-events@^1.8.1:
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.8.1.tgz#d00c7fe93124bbe7c0f27296684838614d24a840"
integrity sha512-8fFnA8EzS3EVbAmpSEUf3A8yZCmfU3IPOSGUDVFCdE1ke1gYL1A+gvXXV6HKUbTPRuvKKt2vpaMbUwYLpDRswQ==
cypress@^12.2.0:
version "12.16.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.16.0.tgz#d0dcd0725a96497f4c60cf54742242259847924c"
integrity sha512-mwv1YNe48hm0LVaPgofEhGCtLwNIQEjmj2dJXnAkY1b4n/NE9OtgPph4TyS+tOtYp5CKtRmDvBzWseUXQTjbTg==
cypress@12.17.0:
version "12.17.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.0.tgz#3a907a41c4afbb44be7b84e822e4914d734a6bb0"
integrity sha512-nq0ug8Zrjq/2khHU1PTNxg+3/n1oqtmAFCxwQhS6QzkQ4mR6RLitX+cGIOuIMfnEbDAtVub0hZh661FOA16JxA==
dependencies:
"@cypress/request" "^2.88.10"
"@cypress/xvfb" "^1.2.4"
@@ -12379,7 +12379,7 @@ cypress@^12.2.0:
pretty-bytes "^5.6.0"
proxy-from-env "1.0.0"
request-progress "^3.0.0"
semver "^7.3.2"
semver "^7.5.3"
supports-color "^8.1.1"
tmp "~0.2.1"
untildify "^4.0.0"
@@ -20881,7 +20881,7 @@ prr@~1.0.1:
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==
psl@^1.1.28, psl@^1.1.33:
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
@@ -20998,10 +20998,12 @@ qs@^6.10.3:
dependencies:
side-channel "^1.0.4"
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
qs@~6.10.3:
version "6.10.5"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4"
integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==
dependencies:
side-channel "^1.0.4"
query-string@6.13.5:
version "6.13.5"
@@ -22269,6 +22271,13 @@ semver@^7.5.1:
dependencies:
lru-cache "^6.0.0"
semver@^7.5.3:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
semver@~7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
@@ -23522,7 +23531,7 @@ toml@^3.0.0:
resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee"
integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==
tough-cookie@^4.1.2:
tough-cookie@^4.1.2, tough-cookie@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
@@ -23532,14 +23541,6 @@ tough-cookie@^4.1.2:
universalify "^0.2.0"
url-parse "^1.5.3"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"